Completed
Push — feature/EVO-7007-selfversion-w... ( 006aa9 )
by
unknown
11:33
created

CoreVersionUtils::runGitInContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 11
loc 11
ccs 0
cts 10
cp 0
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * A service providing functions for getting version numbers
4
 */
5
6
namespace Graviton\CoreBundle\Service;
7
8
use Symfony\Component\Yaml\Parser;
9
use Symfony\Component\Process\Process;
10
use InvalidArgumentException;
11
12
/**
13
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
14
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
15
 * @link     http://swisscom.ch
16
 */
17
class CoreVersionUtils
18
{
19
    /**
20
     * @var string
21
     */
22
    private $composerCmd;
23
24
    /**
25
     * @var string
26
     */
27
    private $rootDir;
28
29
    /**
30
     * @var \Symfony\Component\Yaml\Dumper
31
     */
32
    private $yamlDumper;
33
34
    /**
35
     * @param string                         $composerCmd ComposerCommand
36
     * @param string                         $rootDir     Path to root dir
37
     * @param \Symfony\Component\Yaml\Dumper $yamlDumper  Yaml dumper
38
     */
39
    public function __construct($composerCmd, $rootDir, $yamlDumper)
40
    {
41
        $this->composerCmd = $composerCmd;
42
        $this->rootDir = $rootDir;
43
        $this->yamlDumper = $yamlDumper;
44
    }
45
46
    /**
47
     * gets all versions
48
     *
49
     * @return array version numbers of packages
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
50
     */
51
    public function getPackageVersions()
52
    {
53
        if ($this->isDesiredVersion('self')) {
54
            $versions = [
55
                $this->getContextVersion(),
56
            ];
57
        } else {
58
            $versions = array();
59
        }
60
        $versions = $this->getInstalledPackagesVersion($versions);
61
62
        return $this->yamlDumper->dump($versions);
63
    }
64
65
    /**
66
     * returns the version of graviton or wrapper
67
     *
68
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
69
     */
70
    private function getContextVersion()
71
    {
72
        // get current commit hash
73
        $currentHash = trim($this->runGitInContext('rev-parse --short HEAD'));
74
        // get version from hash:
75
        $version = trim($this->runGitInContext('tag --points-at '.$currentHash));
76
        // if empty, set current branchname to version:
77
        if (!strlen($version)) {
78
            $version = trim($this->runGitInContext('rev-parse --abbrev-ref HEAD'));
79
        }
80
81
        $wrapper['id'] = 'self';
82
        $wrapper['version'] = $version;
83
84
        return $wrapper;
85
    }
86
87
    /**
88
     * returns version for every installed package
89
     *
90
     * @param array $versions versions array
91
     * @return array
92
     */
93
    private function getInstalledPackagesVersion($versions)
94
    {
95
        $output = $this->runComposerInContext('show --installed');
96
        $packages = explode(PHP_EOL, $output);
97
        //last index is always empty
98
        array_pop($packages);
99
100
        foreach ($packages as $package) {
101
            $content = preg_split('/([\s]+)/', $package);
102
            if ($this->isDesiredVersion($content[0])) {
103
                array_push($versions, array('id' => $content[0], 'version' => $content[1]));
104
            }
105
        }
106
107
        return $versions;
108
    }
109
110
    /**
111
     * runs a composer command depending on the context
112
     *
113
     * @param string $command composer args
114
     * @return string
115
     *
116
     * @throws \RuntimeException
117
     * @throws \LogicException
118
     */
119 View Code Duplication
    private function runComposerInContext($command)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
120
    {
121
        $path =  ($this->isWrapperContext())
122
            ? $this->rootDir.'/../../../../'
123
            : $this->rootDir.'/../';
124
        $contextDir = escapeshellarg($path);
125
        $process = new Process('cd '.$contextDir.' && '.escapeshellcmd($this->composerCmd).' '.$command);
126
        $process->mustRun();
127
128
        return $process->getOutput();
129
    }
130
131
    /**
132
     * runs a git command depending on the context
133
     *
134
     * @param string $command git args
135
     * @return string
136
     *
137
     * @throws \RuntimeException
138
     * @throws \LogicException
139
     */
140 View Code Duplication
    private function runGitInContext($command)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
    {
142
        $path =  ($this->isWrapperContext())
143
            ? $this->rootDir.'/../../../../'
144
            : $this->rootDir.'/../';
145
        $contextDir = escapeshellarg($path);
146
        $process = new Process('cd '.$contextDir.' && git '.$command);
147
        $process->mustRun();
148
149
        return $process->getOutput();
150
    }
151
152
    /**
153
     * checks if the package version is configured
154
     *
155
     * @param string $packageName package name
156
     * @return boolean
157
     *
158
     * @throws \RuntimeException
159
     */
160
    private function isDesiredVersion($packageName)
161
    {
162
        if (empty($packageName)) {
163
            throw new \RuntimeException('Missing package name');
164
        }
165
166
        $config = $this->getVersionConfig();
167
168
        if (!empty($config['desiredVersions'])) {
169
            foreach ($config['desiredVersions'] as $confEntry) {
170
                if ($confEntry == $packageName) {
171
                    return true;
172
                }
173
            }
174
        }
175
176
        return false;
177
    }
178
179
    /**
180
     * read and parses version config file
181
     *
182
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
183
     */
184
    public function getVersionConfig()
185
    {
186
        $filePath = $this->isWrapperContext()
187
            ? $this->rootDir . '/../../../../app/config/version_service.yml'
188
            : $this->rootDir . '/config/version_service.yml';
189
190
        return $this->getConfiguration($filePath);
191
    }
192
193
    /**
194
     * checks if context is a wrapper or not
195
     *
196
     * @return boolean
197
     */
198
    private function isWrapperContext()
199
    {
200
        if (strpos($this->rootDir, 'vendor')) {
201
            return true;
202
        } else {
203
            return false;
204
        }
205
    }
206
207
    /**
208
     * reads configuration information from the given file into an array.
209
     *
210
     * @param string $filePath Absolute path to the configuration file.
211
     *
212
     * @return array
213
     */
214
    private function getConfiguration($filePath)
215
    {
216
        $parser = new Parser();
217
        $config = $parser->parse(file_get_contents($filePath));
218
219
        return is_array($config) ? $config : [];
220
    }
221
222
    /**
223
     * Returns the version out of a given version string
224
     *
225
     * @param string $versionString SemVer version string
226
     * @return string
227
     */
228
    public function getVersionNumber($versionString)
229
    {
230
        try {
231
            $version = $this->getVersionOrBranchName($versionString);
232
        } catch (InvalidArgumentException $e) {
233
            $version = $this->normalizeVersionString($versionString);
234
        }
235
236
        return empty($version) ? $versionString : $version;
237
    }
238
239
    /**
240
     * Get a version string string using a regular expression
241
     *
242
     * @param string $versionString SemVer version string
243
     * @return string
244
     */
245
    private function getVersionOrBranchName($versionString)
246
    {
247
        // Regular expression for root package ('self') on a tagged version
248
        $tag = '^(?<version>[v]?[0-9]+\.[0-9]+\.[0-9]+)(?<prerelease>-[0-9a-zA-Z.]+)?(?<build>\+[0-9a-zA-Z.]+)?$';
249
        // Regular expression for root package on a git branch
250
        $branch = '^(?<branch>(dev\-){1}[0-9a-zA-Z\.\/\-\_]+)$';
251
        $regex = sprintf('/%s|%s/', $tag, $branch);
252
253
        $matches = [];
254
        if (0 === preg_match($regex, $versionString, $matches)) {
255
            throw new InvalidArgumentException(
256
                sprintf('"%s" is not a valid SemVer', $versionString)
257
            );
258
        }
259
260
        return empty($matches['version']) ? $matches['branch'] : $matches['version'];
261
    }
262
263
    /**
264
     * Normalizing the incorrect SemVer string to a valid one
265
     *
266
     * At the moment, we are getting the version of the root package ('self') using the
267
     * 'composer show -s'-command. Unfortunately Composer is adding an unnecessary ending.
268
     *
269
     * @param string $versionString SemVer version string
270
     * @param string $prefix        Version prefix
271
     * @return string
272
     */
273
    private function normalizeVersionString($versionString, $prefix = 'v')
274
    {
275
        if (substr_count($versionString, '.') === 3) {
276
            return sprintf(
277
                '%s%s',
278
                $prefix,
279
                implode('.', explode('.', $versionString, -1))
280
            );
281
        }
282
        return $versionString;
283
    }
284
}
285