Test Setup Failed
Push — 17.1 ( 4dae72...46295b )
by Ralf
09:40
created

checkout-build-archives.php ➔ run_cmd()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 3
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
1
#!/usr/bin/php -qC
2
<?php
3
/**
4
 * EGroupware - checkout, build and release archives, build Debian and rpm packages
5
 *
6
 * @link http://www.egroupware.org
7
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
8
 * @author [email protected]
9
 * @copyright (c) 2009-19 by Ralf Becker <[email protected]>
10
 */
11
12
if (php_sapi_name() !== 'cli')	// security precaution: forbit calling setup-cli as web-page
13
{
14
	die('<h1>checkout-build-archives.php must NOT be called as web-page --> exiting !!!</h1>');
15
}
16
date_default_timezone_set('Europe/Berlin');	// to get ride of 5.3 warnings
17
18
$verbose = 0;
19
$config = array(
20
	'packagename' => 'egroupware-epl',
21
	'version' => '17.1',        // '17.1'
22
	'packaging' => date('Ymd'), // '20160520'
23
	'branch'  => 'master',        // checked out branch
24
	'tag' => '$version.$packaging',	// name of tag
25
	'checkoutdir' => realpath(__DIR__.'/../..'),
26
	'egw_buildroot' => '/tmp/build_root/epl_17.1_buildroot',
27
	'sourcedir' => '/home/download/stylite-epl/egroupware-epl-17.1',
28
	/* svn-config currently not used, as we use .mrconfig to define modules and urls
29
	'svntag' => 'tags/$version.$packaging',
30
	'svnbase' => 'svn+ssh://[email protected]/egroupware',
31
	'stylitebase' => 'svn+ssh://[email protected]/stylite',
32
	'svnbranch' => 'branches/16.1',         //'trunk', // 'branches/1.6' or 'tags/1.6.001'
33
	'svnalias' => 'aliases/default-ssh',    // default alias
34
	'extra' => array('$stylitebase/$svnbranch/stylite', '$stylitebase/$svnbranch/esyncpro', '$stylitebase/trunk/archive'),//, '$stylitebase/$svnbranch/groups'), //,'svn+ssh://[email protected]/stylite/trunk/eventmgr'),
35
	*/
36
	'extra' => array('functions' => array('stylite'), 'esyncpro', 'archive',	// create an extra archive for given apps
37
		// these apps are placed in egroupware-epl-contrib archive
38
		'contrib' => array('phpgwapi', 'etemplate', 'jdots', 'phpbrain', 'wiki', 'sambaadmin', 'sitemgr', 'phpfreechat')),
39
	'aliasdir' => 'egroupware',             // directory created by the alias
40
	'types' => array('tar.bz2','tar.gz','zip','all.tar.bz2'),
41
	// add given extra-apps or (uncompressed!) archives to above all.tar.bz2 archive
42
	'all-add' => array('contrib', '/home/stylite/epl-trunk/phpfreechat_data_public.tar'),
43
	// diverse binaries we need
44
	'svn' => trim(`which svn`),
45
	'tar' => trim(`which tar`),
46
	'mv' => trim(`which mv`),
47
	'rm' => trim(`which rm`),
48
	'zip' => trim(`which zip`),
49
	'bzip2' => trim(`which bzip2`),
50
	'clamscan' => trim(`which clamscan`),
51
	'freshclam' => trim(`which freshclam`),
52
	'git' => trim(`which git`),
53
	'gpg' => trim(`which gpg`),
54
	'editor' => trim(`which vi`),
55
	'rsync' => trim(`which rsync`).' --progress -e ssh --exclude "*-stylite-*" --exclude "*-esyncpro-*"',
56
	'composer' => trim(`which composer.phar`),
57
	'after-checkout' => 'rm -rf */source */templates/*/source',
58
	'packager' => '[email protected]',
59
	'obs' => '/home/stylite/obs/stylite-epl-trunk',
60
	'obs_package_alias' => '',	// name used in obs package, if different from packagename
61
	'changelog' => false,   // eg. '* 1. Zeile\n* 2. Zeile' for debian.changes
62
	'changelog_packager' => 'Ralf Becker <[email protected]>',
63
	'editchangelog' => '* ',
64
	//'sfuser' => 'ralfbecker',
65
	//'release' => '$sfuser,[email protected]:/home/frs/project/e/eg/egroupware/eGroupware-$version/eGroupware-$version.$packaging/',
66
	// what gets uploaded with upload
67
	'upload' => '$sourcedir/*egroupware-epl{,-contrib}-$version.$packaging*',
68
	'copychangelog' => '$sourcedir/README', //'$sfuser,[email protected]:/home/frs/project/e/eg/egroupware/README',
69
	'skip' => array(),
70
	'run' => array('checkout','editchangelog','tag','copy','virusscan','create','sign','obs','copychangelog'),
71
	'patchCmd' => '# run cmd after copy eg. "cd $egw_buildroot; patch -p1 /path/to/patch"',
72
	'github_user' => 'ralfbecker',	// Github user for following token
73
	'github_token' => '',	// Github repo personal access token from above user
74
);
75
76
// process config from command line
77
$argv = $_SERVER['argv'];
78
$prog = array_shift($argv);
79
80
while(($arg = array_shift($argv)))
81
{
82
	if ($arg == '-v' || $arg == '--verbose')
83
	{
84
		++$verbose;
85
	}
86
	elseif($arg == '-h' || $arg == '--help')
87
	{
88
		if (in_array('editchangelog', $config['skip']) || !in_array('editchangelog', $config['run']))
89
		{
90
			$config['changelog'] = parse_current_changelog(true);
91
		}
92
		usage();
93
	}
94
	elseif(substr($arg,0,2) == '--' && isset($config[$name=substr($arg,2)]))
95
	{
96
		$value = array_shift($argv);
97
		switch($name)
98
		{
99
			case 'extra':	// stored as array and allow to add/delete items with +/- prefix
100
			case 'types':
101
			case 'skip':
102
			case 'run':
103
			case 'types':
104
			case 'add-all':
105
			case 'modules':
106
				$op = '=';
107
				if (in_array($value[0], array('+', '-')))
108
				{
109
					$op = $value[0];
110
					$value = substr($value, 1);
111
				}
112
				if (in_array($value[0], array('[', '{')) && ($json = json_decode($value, true)))
113
				{
114
					$value = $json;
115
				}
116
				else
117
				{
118
					$value = array_unique(preg_split('/[ ,]+/', $value));
119
				}
120
				switch($op)
121
				{
122
					case '+':
123
						$config[$name] = array_unique(array_merge($config[$name], $value));
124
						break;
125
					case '-':
126
						$config[$name] = array_diff($config[$name], $value);
127
						break;
128
					default:
129
						$config[$name] = $value;
130
				}
131
				break;
132
133
			case 'svntag':
134
			case 'tag':
135
			case 'release':
136
			case 'copychangelog':
137
				$config[$name] = $value;
138
				if ($value) array_unshift($config['run'],$name);
139
				break;
140
141
			case 'editchangelog':
142
				$config[$name] = $value ? $value : true;
143
				if (!in_array('editchangelog',$config['run']))
144
				{
145
					array_unshift($config['run'],'editchangelog');
146
				}
147
				break;
148
149
			case 'obs':
150
				if (!is_dir($value))
151
				{
152
					usage("Path '$value' not found!");
153
				}
154
				if (!in_array('obs',$config['run'])) $config['run'][] = 'obs';
155
				// fall through
156
			default:
157
				$config[$name] = $value;
158
				break;
159
		}
160
	}
161
	else
162
	{
163
		usage("Unknown argument '$arg'!");
164
	}
165
}
166
if ($verbose > 1)
167
{
168
	echo "Using following config:\n".json_encode($config, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)."\n\n";
169
}
170
$svn = $config['svn'];
171
172
$run = array_diff($config['run'],$config['skip']);
173
174
// if we dont edit the changelog, set packaging from changelog
175
if (!in_array('editchangelog', $run))
176
{
177
	parse_current_changelog(true);
178
}
179
foreach($run as $func)
180
{
181
	chdir(dirname(__FILE__));	// make relative filenames work, if other command changes dir
182
	call_user_func('do_'.$func);
183
}
184
185
/**
186
 * Read changelog for given branch from (last) tag or given revision from svn
187
 *
188
 * @param string $_path relativ path to repo starting with $config['aliasdir']
189
 * @param string $log_pattern =null	a preg regular expression or start of line a log message must match, to be returned
190
 * 	if regular perl regular expression given only first expression in brackets \\1 is used,
191
 * 	for a start of line match, only the first line is used, otherwise whole message is used
192
 * @param string& $last_tag =null from which tag on to query logs
193
 * @param string $prefix ='* ' prefix, which if not presend should be added to all log messages
194
 * @return string with changelog
195
 */
196
function get_changelog_from_git($_path, $log_pattern=null, &$last_tag=null, $prefix='* ')
197
{
198
	//echo __FUNCTION__."('$branch_url','$log_pattern','$revision','$prefix')\n";
199
	global $config;
200
201
	$changelog = '';
202
	$path = str_replace($config['aliasdir'], $config['checkoutdir'], $_path);
203
	if (!file_exists($path) || !is_dir($path) || !file_exists($path.'/.git'))
204
	{
205
		throw new Exception("$path is not a git repository!");
206
	}
207
	if (empty($last_tag))
208
	{
209
		$last_tag = get_last_git_tag();
210
	}
211
	if (!empty($last_tag))
212
	{
213
		$cmd = $config['git'].' log '.escapeshellarg($last_tag.'..HEAD');
214
		if (getcwd() != $path) $cmd = 'cd '.$path.'; '.$cmd;
215
		$output = null;
216
		run_cmd($cmd, $output);
217
218
		foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
219
		{
220
			if (substr($line, 0, 4) == "    " && ($msg = _match_log_pattern(substr($line, 4), $log_pattern, $prefix)))
221
			{
222
				$changelog .= $msg."\n";
223
			}
224
		}
225
	}
226
	return $changelog;
227
}
228
229
/**
230
 * Get module path (starting with $config['aliasdir']) per repo from .mrconfig for svn and git
231
 *
232
 * @return array with $repro_url => $path => $url, eg. array(
233
 *		"[email protected]:EGroupware/egroupware.git" => array(
234
 *			"egroupware" => "[email protected]:EGroupware/egroupware.git"),
235
 *		"[email protected]:EGroupware/tracker.git" => array(
236
 *			"egroupware/tracker" => "[email protected]:EGroupware/tracker.git"),
237
 *		"svn+ssh://[email protected]/stylite" => array(
238
 *			"egroupware/stylite] => svn+ssh://[email protected]/stylite/branches/14.2/stylite",
239
 *			"egroupware/esyncpro] => svn+ssh://[email protected]/stylite/branches/14.2/esyncpro",
240
 */
241
function get_modules_per_repo()
242
{
243
	global $config, $verbose;
244
245
	if ($verbose) echo "Get modules from .mrconfig in checkoutdir $config[checkoutdir]\n";
246
247
	if (!is_dir($config['checkoutdir']))
248
	{
249
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
250
	}
251 View Code Duplication
	if (!($mrconfig = file_get_contents($path=$config['checkoutdir'].'/.mrconfig')))
252
	{
253
		throw new Exception("$path not found!");
254
	}
255
	$module = $baseurl = null;
256
	$modules = array();
257
	foreach(explode("\n", $mrconfig) as $line)
258
	{
259
		$matches = null;
260
		if (isset($baseurl))
261
		{
262
			$line = str_replace("\${EGW_REPO_BASE:-\$(git config --get remote.origin.url|sed 's|/egroupware.git||')}", $baseurl, $line);
263
		}
264
		if ($line && $line[0] == '[' && preg_match('/^\[([^]]*)\]/', $line, $matches))
265
		{
266
			if (in_array($matches[1], array('DEFAULT', 'vendor/egroupware/ckeditor', 'api/src/Accounts/Ads', 'phpgwapi/js/ckeditor', 'phpgwapi/inc/adldap')))
267
			{
268
				$module = null;
269
				continue;
270
			}
271
			$module = (string)$matches[1];
272
		}
273
		elseif (isset($module) && preg_match('/^checkout\s*=\s*(git\s+clone\s+(-b\s+[0-9.]+\s+)?((git|http)[^ ]+)|svn\s+checkout\s+((svn|http)[^ ]+))/', $line, $matches))
274
		{
275
			$repo = $url = substr($matches[1], 0, 3) == 'svn' ? $matches[5] : $matches[3];
276
			if (substr($matches[1], 0, 3) == 'svn') $repo = preg_replace('#/(trunk|branches)/.*$#', '', $repo);
277
			$modules[$repo][$config['aliasdir'].($module ? '/'.$module : '')] = $url;
278
			if ($module === '' && !isset($baseurl)) $baseurl = str_replace('/egroupware.git', '', $url);
279
		}
280
	}
281
	if ($verbose) print_r($modules);
282
	return $modules;
283
}
284
285
/**
286
 * Get commit of last git tag matching a given pattern
287
 *
288
 * @return string name of last tag matching $config['version'].'.*'
289
 */
290
function get_last_git_tag()
291
{
292
	global $config;
293
294
	if (!is_dir($config['checkoutdir']))
295
	{
296
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
297
	}
298
	chdir($config['checkoutdir']);
299
300
	$cmd = $config['git'].' tag -l '.escapeshellarg($config['version'].'.*');
301
	$output = null;
302
	run_cmd($cmd, $output);
303
	array_shift($output);
304
305
	return trim(array_pop($output));
306
}
307
308
/**
309
 * Checkout or update EGroupware
310
 *
311
 * Ensures an existing checkout is from the correct branch! Otherwise it get's deleted
312
 */
313
function do_checkout()
314
{
315
	global $config;
316
317
	echo "Starting checkout/update\n";
318
	if (!file_exists($config['checkoutdir']))
319
	{
320
		$cmd = $config['git'].' clone '.(!empty($config['branch']) ? ' -b '.$config['branch'] : '').
321
			' [email protected]:EGroupware/egroupware.git '.$config['checkoutdir'];
322
		run_cmd($cmd);
323
	}
324 View Code Duplication
	elseif (!is_dir($config['checkoutdir']) || !is_writable($config['checkoutdir']))
325
	{
326
		throw new Exception("checkout directory '{$config['checkoutdir']} exists and is NO directory or NOT writable!");
327
	}
328
	chdir($config['checkoutdir']);
329
330
	run_cmd('./install-php --ignore-platform-reqs --no-dev');
331
}
332
333
/**
334
 * Create a tag using mr in svn or git for current checked out branch
335
 */
336
function do_tag()
337
{
338
	global $config;
339
340
	if (!is_dir($config['checkoutdir']))
341
	{
342
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
343
	}
344
	chdir($config['checkoutdir']);
345
346
	$config['tag'] = config_translate('tag');	// allow to use config vars like $version in tag
347
348
	if (empty($config['tag'])) return;	// otherwise we copy everything in svn root!
349
350
	echo "Creating tag and pushing $config[tag]\n";
351
352
	run_cmd('./install-cli.php --git tag '.escapeshellarg($config['tag']).' '.escapeshellarg('Creating '.$config['tag']));
353
354
	// push tags in all apps (not main-dir!)
355
	run_cmd('./install-cli.php --git-app push origin '.$config['tag']);
356
357
	// checkout tag, update composer.{json,lock}, move tag to include them
358
	run_cmd($config['git'].' checkout '.$config['tag']);
359
	update_composer_json_version($config['tag']);
360
	// might require more then one run, as pushed tags need to be picked up by packagist
361
	$output = $ret = null;
362
	$timeout = 10;
363
	for($try=1; $try < 10 && run_cmd($config['composer'].' update egroupware/\*', $output, 2); ++$try)
364
	{
365
		echo "$try. retry in $timeout seconds ...\n";
366
		sleep($timeout);
367
	}
368
	run_cmd($config['git'].' commit -m '.escapeshellarg('Updating dependencies for '.$config['tag']).' composer.{json,lock}');
369
	run_cmd($config['git'].' tag -f '.escapeshellarg($config['tag']).' -m '.escapeshellarg('Updating dependencies for '.$config['tag']));
370
}
371
372
/**
373
 * Update composer.json with version number (or add it after "name" if not yet there)
374
 *
375
 * @param string $version
376
 * @throws Exception on error
377
 */
378
function update_composer_json_version($version)
379
{
380
	global $config;
381
382 View Code Duplication
	if (!($json = file_get_contents($path=$config['checkoutdir'].'/composer.json')))
383
	{
384
		throw new Exception("Can NOT read $path to update with new version!");
385
	}
386
	if (preg_match('/"version":\s*"[^"]+"/', $json))
387
	{
388
		$json = preg_replace('/"version":\s*"[^"]+"/', '"version": "'.$version.'"', $json);
389
	}
390
	elseif (preg_replace('/^(\s*)"name":\s*"[^"]+",$/m', $json))
391
	{
392
		$json = preg_replace('/^(\s*)"name":\s*"[^"]+",$/m', '$0'."\n".'$1"version": "'.$version.'",', $json);
393
	}
394
	else
395
	{
396
		throw new Exception("Failed to add new version to $path!");
397
	}
398
	if (!file_put_contents($path, $json))
399
	{
400
		throw new Exception("Can NOT update $path with new version!");
401
	}
402
}
403
404
/**
405
 * Release sources by rsync'ing them to a distribution / download directory
406
 */
407
function do_release()
408
{
409
	global $config,$verbose;
410
411
	// push local changes to Github incl. tag (tags of apps are already pushed by do_tag)
412
	if ($verbose) echo "Pushing changes and tags\n";
413
	chdir($config['checkoutdir']);
414
	run_cmd($config['git'].' push');
415
	$tag = config_translate('tag');
416
	run_cmd($config['git'].' push origin '.$tag);
417
	chdir($config['checkoutdir']);
418
419
	if (empty($config['github_user']) || empty($config['github_token']))
420
	{
421
		throw new Exception("No personal Github user or access token specified (--github_token)!");
422
	}
423
	if (empty($config['changelog']))
424
	{
425
		$config['changelog'] = parse_current_changelog();
426
	}
427
	$data = array(
428
		'tag_name' => $tag,
429
		'name' => $tag,
430
		'target_commitish' => $config['branch'],
431
		'body' => $config['changelog'],
432
	);
433
	$response = github_api("/repos/EGroupware/egroupware/releases", $data);
434
	$config['upload_url'] = preg_replace('/{\?[^}]+}$/', '', $response['upload_url']);	// remove {?name,label} template
435
436
	do_upload();
437
}
438
439
/**
440
 * Upload archives
441
 */
442
function do_upload()
443
{
444
	global $config,$verbose;
445
446
	if (empty($config['upload_url']))
447
	{
448
		$response = github_api("/repos/EGroupware/egroupware/releases", array(), 'GET');
449
		$config['upload_url'] = preg_replace('/{\?[^}]+}$/', '', $response[0]['upload_url']);	// remove {?name,label} template
450
	}
451
452
	$archives = config_translate('upload');
453
	echo "Uploading $archives to $config[upload_url]\n";
454
455
	foreach(glob($archives, GLOB_BRACE) as $path)
456
	{
457
		$label = null;
458
		if (substr($path, -4) == '.zip')
459
		{
460
			$content_type = 'application/zip';
461
		}
462
		elseif(substr($path, -7) == '.tar.gz')
463
		{
464
			$content_type = 'application/x-gzip';
465
		}
466
		elseif(substr($path, -8) == '.tar.bz2')
467
		{
468
			$content_type = 'application/x-bzip2';
469
		}
470
		elseif(substr($path, -8) == '.txt.asc')
471
		{
472
			$content_type = 'text/plain';
473
			$label = 'Signed hashes of downloads';
474
		}
475
		else
476
		{
477
			continue;
478
		}
479
		if ($verbose) echo "Uploading $path as $content_type\n";
480
		$name = basename($path);
481
		github_api($config['upload_url'], array(
482
			'name' => $name,
483
			'label' => isset($label) ? $label : $name,
484
		), 'FILE', $path, $content_type);
485
	}
486
487
	if (!empty($config['release']))
488
	{
489
		$target = config_translate('release');	// allow to use config vars like $svnbranch in module
490
		$cmd = $config['rsync'].' '.$archives.' '.$target;
491
		if ($verbose) echo $cmd."\n";
492
		passthru($cmd);
493
	}
494
}
495
496
/**
497
 * Sending a Github API request
498
 *
499
 * @param string $_url url of just path where to send request to (https://api.github.com is added automatic)
500
 * @param string|array $data payload, array get automatic added as get-parameter or json_encoded for POST
501
 * @param string $method ='POST'
502
 * @param string $upload =null path of file to upload, payload for request with $method='FILE'
503
 * @param string $content_type =null
504
 * @throws Exception
505
 * @return array with response
506
 */
507
function github_api($_url, $data, $method='POST', $upload=null, $content_type=null)
508
{
509
	global $config, $verbose;
510
511
	$url = $_url[0] == '/' ? 'https://api.github.com'.$_url : $_url;
512
	$c = curl_init();
513
	curl_setopt($c, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
514
	curl_setopt($c, CURLOPT_USERPWD, $config['github_user'].':'.$config['github_token']);
515
	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
516
	curl_setopt($c, CURLOPT_USERAGENT, basename(__FILE__));
517
	curl_setopt($c, CURLOPT_TIMEOUT, 240);
518
	curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
519
520 View Code Duplication
	switch($method)
521
	{
522
		case 'POST':
523
			curl_setopt($c, CURLOPT_POST, true);
524
			if (is_array($data)) $data = json_encode($data, JSON_FORCE_OBJECT);
525
			curl_setopt($c, CURLOPT_POSTFIELDS, $data);
526
			break;
527
		case 'GET':
528
			if(count($data)) $url .= '?' . http_build_query($data);
529
			break;
530
		case 'FILE':
531
			curl_setopt($c, CURLOPT_HTTPHEADER, array("Content-type: $content_type"));
532
			curl_setopt($c, CURLOPT_POST, true);
533
			curl_setopt($c, CURLOPT_POSTFIELDS, file_get_contents($upload));
534
			if(count($data)) $url .= '?' . http_build_query($data);
535
			break;
536
		default:
537
			throw new Exception(__FUNCTION__.": Unknown/unimplemented method=$method!");
538
	}
539
	curl_setopt($c, CURLOPT_URL, $url);
540
541 View Code Duplication
	if (is_string($data)) $short_data = strlen($data) > 100 ? substr($data, 0, 100).' ...' : $data;
542 View Code Duplication
	if ($verbose) echo "Sending $method request to $url ".(isset($short_data)&&$method!='GET'?$short_data:'')."\n";
543
544 View Code Duplication
	if (($response = curl_exec($c)) === false)
545
	{
546
		// run failed request again to display response including headers
547
		curl_setopt($c, CURLOPT_HEADER, true);
548
		curl_setopt($c, CURLOPT_RETURNTRANSFER, false);
549
		curl_exec($c);
550
		throw new Exception("$method request to $url failed ".(isset($short_data)&&$method!='GET'?$short_data:''));
551
	}
552
553 View Code Duplication
	if ($verbose) echo (strlen($response) > 200 ? substr($response, 0, 200).' ...' : $response)."\n";
554
555
	curl_close($c);
556
557
	return json_decode($response, true);
558
}
559
560
/**
561
 * Fetch a config value allowing to use config vars like $svnbranch in it
562
 *
563
 * @param string $name
564
 * @param string $value =null value to use, default $config[$name]
565
 */
566
function config_translate($name, $value=null)
567
{
568
	global $config;
569
570
	if (!isset($value)) $value = $config[$name];
571
	if (is_string($value) && strpos($value, '$') !== false)
572
	{
573
		$translate = array();
574
		foreach($config as $n => $val)
575
		{
576
			if (is_string($val)) $translate['$'.$n] = $val;
577
		}
578
		$value = strtr($value, $translate);
579
	}
580
	return $value;
581
}
582
583
/**
584
 * Copy changelog by rsync'ing it to a distribution / download directory
585
 */
586
function do_copychangelog()
587
{
588
	global $config;
589
590
	$changelog = __DIR__.'/debian.changes';
591
	$cmd = $config['rsync'].' '.$changelog.' '.config_translate('copychangelog');
592
	passthru($cmd);
593
}
594
595
/**
596
 * Query changelog and let user edit it
597
 */
598
function do_editchangelog()
599
{
600
	global $config,$svn;
601
602
	echo "Querying changelog from Git/SVN\n";
603
	if (!isset($config['modules']))
604
	{
605
		$config['modules'] = get_modules_per_repo();
606
	}
607
	// query changelog per repo
608
	$changelog = '';
609
	$last_tag = null;
610
	foreach($config['modules'] as $branch_url => $modules)
611
	{
612
		$revision = null;
613
		if (substr($branch_url, -4) == '.git')
614
		{
615
			list($path) = each($modules);
616
			$changelog .= get_changelog_from_git($path, $config['editchangelog'], $last_tag);
617
		}
618
		else
619
		{
620
			$changelog .= get_changelog_from_svn($branch_url, $config['editchangelog'], $revision);
621
		}
622
	}
623
	if (empty($changelog))
624
	{
625
		$changelog = "Could not query changelog for $config[version], eg. no last tag found!\n";
626
	}
627
	$logfile = tempnam('/tmp','checkout-build-archives');
628
	file_put_contents($logfile,$changelog);
629
	$cmd = $config['editor'].' '.escapeshellarg($logfile);
630
	passthru($cmd);
631
	$config['changelog'] = file_get_contents($logfile);
632
	// remove trailing newlines
633
	while (substr($config['changelog'],-1) == "\n")
634
	{
635
		$config['changelog'] = substr($config['changelog'],0,-1);
636
	}
637
	// allow user to abort, by deleting the changelog
638
	if (strlen($config['changelog']) <= 2)
639
	{
640
		die("\nChangelog must not be empty --> aborting\n\n");
641
	}
642
	// commit changelog
643
	$changelog = $config['checkoutdir'].'/doc/rpm-build/debian.changes';
644
	if (!file_exists($changelog))
645
	{
646
		throw new Exception("Changelog '$changelog' not found!");
647
	}
648
	file_put_contents($changelog, update_changelog(file_get_contents($changelog)));
649
650
	update_api_setup($api_setup=$config['checkoutdir'].'/api/setup/setup.inc.php');
651
652
	if (file_exists($config['checkoutdir'].'/.git'))
653
	{
654
		$cmd = $config['git']." commit -m 'Changelog for $config[version].$config[packaging]' ".$changelog.' '.$api_setup;
655
	}
656
	else
657
	{
658
		$cmd = $svn." commit -m 'Changelog for $config[version].$config[packaging]' ".$changelog.' '.$api_setup;
659
	}
660
	run_cmd($cmd);
661
662
	// update obs changelogs (so all changlogs are updated in case of a later error and changelog step can be skiped)
663
	do_obs(true);	// true: only update debian.changes in obs checkouts
664
}
665
666
/**
667
 * Read changelog for given branch from (last) tag or given revision from svn
668
 *
669
 * @param string $branch_url ='svn+ssh://[email protected]/egroupware/branches/Stylite-EPL-10.1'
670
 * @param string $log_pattern =null	a preg regular expression or start of line a log message must match, to be returned
671
 * 	if regular perl regular expression given only first expression in brackets \\1 is used,
672
 * 	for a start of line match, only the first line is used, otherwise whole message is used
673
 * @param string& $revision =null from which to HEAD the log should be retrieved, default search revision of latest tag in ^/tags
674
 * @param string $prefix ='* ' prefix, which if not presend should be added to all log messages
675
 * @return string with changelog
676
 */
677
function get_changelog_from_svn($branch_url, $log_pattern=null, &$revision=null, $prefix='* ')
678
{
679
	//echo __FUNCTION__."('$branch_url','$log_pattern','$revision','$prefix')\n";
680
	global $config,$verbose,$svn;
681
682
	if (is_null($revision))
683
	{
684
		list($tags_url,$branch) = preg_split('#/(branches/|trunk)#',$branch_url);
685
		if (empty($branch)) $branch = $config['version'];
686
		$tags_url .= '/tags';
687
		$pattern='|/tags/('.preg_quote($config['version'], '|').'\.[0-9.]+)|';
688
		$matches = null;
689
		$revision = get_last_svn_tag($tags_url,$pattern,$matches);
690
		$tag = $matches[1];
691
	}
692
	elseif(!is_numeric($revision))
693
	{
694
		$revision = get_last_svn_tag($tags_url,$tag=$revision);
0 ignored issues
show
Bug introduced by
The variable $tags_url seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
695
	}
696
	$cmd = $svn.' log --xml -r '.escapeshellarg($revision.':HEAD').' '.escapeshellarg($branch_url);
697
	if (($v = $verbose))
698
	{
699
		echo "Querying SVN for log from r$revision".($tag ? " ($tag)" : '').":\n$cmd\n";
700
		$verbose = false;	// otherwise no $output!
701
	}
702
	$output = array();
703
	run_cmd($cmd,$output);
704
	$verbose = $v;
705
	array_shift($output);	// remove the command
706
707
	$xml = simplexml_load_string($output=implode("\n",$output));
708
	$message = '';
709
	foreach($xml as $log)
710
	{
711
		if (!($msg = _match_log_pattern($log->msg, $log_pattern, $prefix))) continue;	// no match --> ignore
712
713
		$message .= $msg."\n";
714
	}
715
	if ($verbose) echo $message;
716
717
	return $message;
718
}
719
720
/**
721
 * Return first row of matching log lines always prefixed with $prefix
722
 *
723
 * @param string $msg whole log message
724
 * @param string $log_pattern
725
 * @param string $prefix ='* '
726
 * @return string
727
 */
728
function _match_log_pattern($msg, $log_pattern, $prefix='* ')
729
{
730
	$pattern_len = strlen($log_pattern);
731
	$prefix_len = strlen($prefix);
732
733
	$matches = null;
734
	if ($log_pattern[0] == '/' && preg_match($log_pattern,$msg,$matches))
735
	{
736
		$msg = $matches[1];
737
	}
738
	elseif($log_pattern && $log_pattern[0] != '/' && substr($msg,0,$pattern_len) == $log_pattern)
739
	{
740
		list($msg) = explode("\n",$msg);
741
	}
742
	elseif($log_pattern)
743
	{
744
		return null;
745
	}
746
	if ($prefix_len && substr($msg,0,$prefix_len) != $prefix) $msg = $prefix.$msg;
747
748
	return $msg;
749
}
750
751
/**
752
 * Get revision of last svn tag matching a given pattern in the log message
753
 *
754
 * @param string $tags_url
755
 * @param string $pattern which has to be contained in the log message (NOT the tag itself)
756
 * 	or (perl) regular expression against which log message is matched
757
 * @param array &$matches=null on return matches of preg_match
758
 * @return int revision of last svn tag matching pattern
759
 */
760
function get_last_svn_tag($tags_url,$pattern,&$matches=null)
761
{
762
	global $verbose,$svn;
763
764
	$cmd = $svn.' log --xml --limit 40 -v '.escapeshellarg($tags_url);
765
	if (($v = $verbose))
766
	{
767
		echo "Querying SVN for last tags\n$cmd\n";
768
		$verbose = false;	// otherwise no $output!
769
	}
770
	$output = array();
771
	run_cmd($cmd,$output);
772
	$verbose = $v;
773
	array_shift($output);	// remove the command
774
775
	$xml = simplexml_load_string($output=implode("\n",$output));
776
	$is_regexp = $pattern[0] == substr($pattern, -1);
777
	foreach($xml as $log)
778
	{
779
		//print_r($log);
780
		if (!$is_regexp && strpos($log->paths->path, $pattern) !== false ||
781
			$is_regexp && preg_match($pattern, $log->paths->path, $matches))
782
		{
783
			if ($verbose) echo "Revision {$log['revision']} matches".($matches?': '.$matches[1] : '')."\n";
784
			return (int)$log['revision'];
785
		}
786
	}
787
	return null;
788
}
789
790
/**
791
 * Copy archive files to obs checkout and commit them
792
 *
793
 * @param boolean $only_update_changelog =false true update debian.changes, but nothing else, nor commit it
794
 */
795
function do_obs($only_update_changelog=false)
796
{
797
	global $config,$verbose;
798
799
	if (!is_dir($config['obs']))
800
	{
801
		usage("Path '$config[obs]' not found!");
802
	}
803
	if ($verbose) echo $only_update_changelog ? "Updating OBS changelogs\n" : "Updating OBS checkout\n";
804
	run_cmd('osc up '.$config['obs']);
805
806
	$n = 0;
807
	foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config['obs'])) as $path)
808
	{
809
		if (basename(dirname($path)) == '.osc' ||
810
			!preg_match('/\/('.preg_quote($config['packagename']).
811
				($config['obs_package_alias'] ? '|'.preg_quote($config['obs_package_alias']) : '').
812
				')[a-z-]*-('.preg_quote($config['version']).'|trunk)/',$path))
813
		{
814
			continue;
815
		}
816
		$matches = null;
817
		if (preg_match('/\/('.preg_quote($config['packagename']).'[a-z-]*)-'.preg_quote($config['version']).'\.[0-9.]+[0-9](\.tar\.(gz|bz2))$/',$path,$matches) &&
818
			file_exists($new_name=$config['sourcedir'].'/'.$matches[1].'-'.$config['version'].'.'.$config['packaging'].$matches[2]))
819
		{
820
			if (basename($path) != basename($new_name))
821
			{
822
				unlink($path);
823
				if ($verbose) echo "rm $path\n";
824
			}
825
			copy($new_name,dirname($path).'/'.basename($new_name));
826
			if ($verbose) echo "cp $new_name ".dirname($path)."/\n";
827
			++$n;
828
		}
829
		// if we have no changelog (eg. because commands run separate), try parsing it from changelog file
830
		if (empty($config['changelog']))
831
		{
832
			$config['changelog'] = parse_current_changelog();
833
		}
834
		// updating dsc, spec and changelog files
835
		if (!$only_update_changelog && (substr($path,-4) == '.dsc' || substr($path,-5) == '.spec') ||
836
			!empty($config['changelog']) && basename($path) == 'debian.changes')
837
		{
838
			$content = $content_was = file_get_contents($path);
839
840
			if (substr($path,-4) == '.dsc' || substr($path,-5) == '.spec')
841
			{
842
				$content = preg_replace('/^Version: '.preg_quote($config['version']).'\.[0-9.]+[0-9]/m','Version: '.$config['version'].'.'.$config['packaging'],$content);
843
			}
844
			if (substr($path,-4) == '.dsc')
845
			{
846
				$content = preg_replace('/^(Debtransform-Tar: '.preg_quote($config['packagename']).'[a-z-]*)-'.
847
					preg_quote($config['version']).'\.[0-9.]+[0-9](\.tar\.(gz|bz2))$/m',
848
					'\\1-'.$config['version'].'.'.$config['packaging'].'\\2',$content);
849
			}
850
			if (basename($path) == 'debian.changes' && strpos($content,$config['version'].'.'.$config['packaging']) === false)
851
			{
852
				$content = update_changelog($content);
853
			}
854
			if (!empty($config['changelog']) && substr($path,-5) == '.spec' &&
855
				($pos_changelog = strpos($content,'%changelog')) !== false)
856
			{
857
				$pos_changelog += strlen("%changelog\n");
858
				$content = substr($content,0,$pos_changelog).' *'.date('D M d Y').' '.$config['changelog_packager']."\n".
859
					$config['changelog']."\n".substr($content,$pos_changelog);
860
			}
861
			if ($content != $content_was)
862
			{
863
				file_put_contents($path,$content);
864
				if ($verbose) echo "Updated $path\n";
865
				++$n;
866
			}
867
		}
868
	}
869
	if ($n && !$only_update_changelog)
870
	{
871
		echo "$n files updated in OBS checkout ($config[obs]), commiting them now...\n";
872
		//run_cmd('osc status '.$config['obs']);
873
		run_cmd('osc addremove '.$config['obs'].'/*');
874
		run_cmd('osc commit -m '.escapeshellarg('Version: '.$config['version'].'.'.$config['packaging'].":\n".$config['changelog']).' '.$config['obs']);
875
	}
876
}
877
878
/**
879
 * Parse current changelog from debian.changes file
880
 *
881
 * @param boolean $set_packaging =false true: set packaging from last changelog entry
882
 * @return string changelog entries without header and footer lines
883
 */
884
function parse_current_changelog($set_packaging=false)
885
{
886
	global $config;
887
888
	$changelog = file_get_contents($config['checkoutdir'].'/doc/rpm-build/debian.changes');
889
	$lines = explode("\n", $changelog, 100);
890
	$matches = null;
891
	foreach($lines as $n => $line)
892
	{
893
		if (preg_match($preg='/^'.preg_quote($config['packagename']).' \('.preg_quote($config['version']).'\.'.
894
			($set_packaging ? '([0-9]+)' : preg_quote($config['packaging'])).'/', $line, $matches))
895
		{
896
			if ($set_packaging)
897
			{
898
				$config['packaging'] = $matches[1];
899
			}
900
			$n += empty($lines[$n+1]) ? 2 : 1;	// overead empty line behind header
901
			$logentry = '';
902
			while($lines[$n])	// entry is terminated by empty line
903
			{
904
				$logentry .= (substr($lines[$n], 0, 2) == '  ' ? substr($lines[$n], 2) : $lines[$n])."\n";
905
				++$n;
906
			}
907
			return substr($logentry, 0, -1);	// remove training "\n"
908
		}
909
	}
910
	return null;	// paragraph for current version NOT found
911
}
912
913
/**
914
 * Update content of debian changelog file with new content from $config[changelog]
915
 *
916
 * @param string $content existing changelog content
917
 * @return string updated changelog content
918
 */
919
function update_changelog($content)
920
{
921
	global $config;
922
923
	list($header) = explode("\n", $content);
924
	$new_header = preg_replace('/\('.preg_quote($config['version']).'\.[0-9.]+[0-9](.*)\)/','('.$config['version'].'.'.$config['packaging'].'\\1)', $header);
925
	if (substr($config['changelog'],0,2) != '  ') $config['changelog'] = '  '.implode("\n  ",explode("\n",$config['changelog']));
926
	$content = $new_header."\n\n".$config['changelog'].
927
		"\n\n -- ".$config['changelog_packager'].'  '.date('r')."\n\n".$content;
928
929
	return $content;
930
}
931
932
/**
933
 * Update content of api/setup/setup.inc.php file with new maintenance version
934
 *
935
 * @param string $path full path to "api/setup/setup.inc.php"
936
 */
937
function update_api_setup($path)
938
{
939
	global $config;
940
941
	if (!($content = file_get_contents($path)))
942
	{
943
		throw new Exception("Could not read file '$path' to update maintenance-version!");
944
	}
945
946
	$content = preg_replace('/'.preg_quote("\$setup_info['api']['versions']['maintenance_release']", '/').'[^;]+;/',
947
		"\$setup_info['api']['versions']['maintenance_release'] = '".$config['version'].'.'.$config['packaging']."';",
948
		$content);
949
950
	if (!file_put_contents($path, $content))
951
	{
952
		throw new Exception("Could not update file '$path' with maintenance-version!");
953
	}
954
}
955
956
/**
957
 * Sign sha1sum file
958
 */
959
function do_sign()
960
{
961
	global $config;
962
963 View Code Duplication
	if (substr($config['sourcedir'],0,2) == '~/')	// sha1_file cant deal with '~/rpm'
964
	{
965
		$config['sourcedir'] = getenv('HOME').substr($config['sourcedir'],1);
966
	}
967
	$sumsfile = $config['sourcedir'].'/sha1sum-'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.txt';
968
969
	if (!file_exists($sumsfile))
970
	{
971
		echo "sha1sum file '$sumsfile' not found!\n";
972
		return;
973
	}
974
	// signing it
975
	if (empty($config['gpg']) || !file_exists($config['gpg']))
976
	{
977
		if (!empty($config['gpg'])) echo "{$config['gpg']} not found --> skipping signing sha1sum file!\n";
978
		return;
979
	}
980
	echo "Signing sha1sum file:\n";
981
	if (file_exists($sumsfile.'.asc')) unlink($sumsfile.'.asc');
982
	$cmd = $config['gpg'].' --local-user '.$config['packager'].' --clearsign '.$sumsfile;
983
	run_cmd($cmd);
984
	unlink($sumsfile);	// delete the unsigned file
985
}
986
987
/**
988
 * Create archives
989
 */
990
function do_create()
991
{
992
	global $config;
993
994
	if (!file_exists($config['sourcedir'])) mkdir($config['sourcedir'],0755,true);
995 View Code Duplication
	if (substr($config['sourcedir'],0,2) == '~/')	// sha1_file cant deal with '~/rpm'
996
	{
997
		$config['sourcedir'] = getenv('HOME').substr($config['sourcedir'],1);
998
	}
999
	$sumsfile = $config['sourcedir'].'/sha1sum-'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.txt';
1000
	$sums = '';
1001
1002
	chdir($config['egw_buildroot']);
1003
1004
	if($config['extra'])
1005
	{
1006
		$exclude = $exclude_all = array();
1007
		foreach($config['extra'] as $name => $modules)
1008
		{
1009
			foreach((array)$modules as $module)
1010
			{
1011
				$exclude[] = basename($module);
1012
				if (!empty($config['all-add']) && !in_array($module, $config['all-add']) && (is_int($name) || !in_array($name, $config['all-add'])))
1013
				{
1014
					$exclude_all[] = basename($module);
1015
				}
1016
			}
1017
		}
1018
		$exclude_extra = ' --exclude=egroupware/'.implode(' --exclude=egroupware/', $exclude);
1019
		$exclude_all_extra =  $exclude_all ? ' --exclude=egroupware/'.implode(' --exclude=egroupware/', $exclude_all) : '';
1020
	}
1021
	foreach($config['types'] as $type)
1022
	{
1023
		echo "Creating $type archives\n";
1024
		$tar_type = $type == 'tar.bz2' ? 'j' : 'z';
1025
1026
		$file = $config['sourcedir'].'/'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.'.$type;
1027
		switch($type)
1028
		{
1029
			case 'all.tar.bz2':	// single tar-ball for debian builds not easily supporting to use multiple
1030
				$file = $config['sourcedir'].'/'.$config['packagename'].'-all-'.$config['version'].'.'.$config['packaging'].'.tar';
1031
				$cmd = $config['tar'].' --owner=root --group=root -cf '.$file.$exclude_all_extra.' egroupware';
1032
				if (!empty($config['all-add']))
1033
				{
1034
					foreach((array)$config['all-add'] as $add)
1035
					{
1036
						if (substr($add, -4) != '.tar') continue;	// probably a module
1037
						if (!($tar = realpath($add))) throw new Exception("File '$add' not found!");
1038
						$cmd .= '; '.$config['tar'].' --owner=root --group=root -Af '.$file.' '.$tar;
1039
					}
1040
				}
1041
				if (file_exists($file.'.bz2')) $cmd .= '; rm -f '.$file.'.bz2';
1042
				$cmd .= '; '.$config['bzip2'].' '.$file;
1043
				// run cmd now and continue without adding all tar-ball to sums, as we dont want to publish it
1044
				run_cmd($cmd);
1045
				continue 2;
1046
			case 'tar.bz2':
1047 View Code Duplication
			case 'tar.gz':
1048
				$cmd = $config['tar'].' --owner=root --group=root -c'.$tar_type.'f '.$file.$exclude_extra.' egroupware';
1049
				break;
1050
			case 'zip':
1051
				$cmd = file_exists($file) ? $config['rm'].' -f '.$file.'; ' : '';
1052
				$cmd .= $config['mv'].' egroupware/'.implode(' egroupware/', $exclude).' . ;';
1053
				$cmd .= $config['zip'].' -q -r -9 '.$file.' egroupware ;';
1054
				$cmd .= $config['mv'].' '.implode(' ', $exclude).' egroupware';
1055
				break;
1056
		}
1057
		run_cmd($cmd);
1058
		$sums .= sha1_file($file)."\t".basename($file)."\n";
1059
1060
		foreach($config['extra'] as $name => $modules)
1061
		{
1062
			if (is_numeric($name)) $name = $modules;
1063
			$dirs = ' egroupware/'.implode(' egroupware/', (array)$modules);
1064
			$file = $config['sourcedir'].'/'.$config['packagename'].'-'.$name.'-'.$config['version'].'.'.$config['packaging'].'.'.$type;
1065
			switch($type)
1066
			{
1067
				case 'all.tar.bz2':
1068
					break;	// nothing to do
1069
				case 'tar.bz2':
1070 View Code Duplication
				case 'tar.gz':
1071
					$cmd = $config['tar'].' --owner=root --group=root -c'.$tar_type.'f '.$file.$dirs;
1072
					break;
1073
				case 'zip':
1074
					$cmd = file_exists($file) ? $config['rm'].' -f '.$file.'; ' : '';
1075
					$cmd .= $config['zip'].' -q -r -9 '.$file.$dirs;
1076
					break;
1077
			}
1078
			run_cmd($cmd);
1079
			$sums .= sha1_file($file)."\t".basename($file)."\n";
1080
		}
1081
	}
1082
	// writing sha1sum file
1083
	file_put_contents($sumsfile,$sums);
1084
}
1085
1086
/**
1087
 * Scan checkout for viruses, if clamscan is installed (not fatal if not!)
1088
 */
1089
function do_virusscan()
1090
{
1091
	global $config,$verbose;
1092
1093
	if (!file_exists($config['clamscan']) || !is_executable($config['clamscan']))
1094
	{
1095
		echo "Virusscanner '$config[clamscan]' not found --> skipping virus scan!\n";
1096
		return;
1097
	}
1098
	// try updating virus database
1099
	if (file_exists($config['freshclam']))
1100
	{
1101
		echo "Updating virus signatures\n";
1102
		$cmd = '/usr/bin/sudo '.$config['freshclam'];
1103
		if (!$verbose && function_exists('posix_getuid') && posix_getuid()) echo $cmd."\n";
1104
		$output = null;
1105
		run_cmd($cmd,$output,1);	// 1 = ignore already up to date database
1106
	}
1107
	echo "Starting virusscan\n";
1108
	$cmd = $config['clamscan'].' --quiet -r '.$config['egw_buildroot'];
1109
	run_cmd($cmd);
1110
	echo "Virusscan successful (no viruses found).\n";
1111
}
1112
1113
/**
1114
 * Copy non .svn/.git parts to egw_buildroot and fix permissions and ownership
1115
 *
1116
 * We need to stash local modifications (currently only in egroupware main module) to revert eg. .mrconfig modifications
1117
 */
1118
function do_copy()
1119
{
1120
	global $config;
1121
1122
	// copy everything, but .svn dirs from checkoutdir to egw_buildroot
1123
	echo "Copying non-svn/git/tests dirs to buildroot\n";
1124
1125
	if (!file_exists($config['egw_buildroot']))
1126
	{
1127
		run_cmd("mkdir -p $config[egw_buildroot]");
1128
	}
1129
1130
	// we need to stash uncommited changes like .mrconfig, before copying
1131
	if (file_exists($config['checkoutdir'].'/.git')) run_cmd("cd $config[checkoutdir]; git stash");
1132
1133
	try {
1134
		$cmd = '/usr/bin/rsync -r --delete --delete-excluded --exclude .svn --exclude .git\* --exclude .mrconfig --exclude node_modules/ --exclude tests '.$config['checkoutdir'].'/ '.$config['egw_buildroot'].'/'.$config['aliasdir'].'/';
1135
		run_cmd($cmd);
1136
	}
1137
	catch (Exception $e) {
1138
		// catch failures to pop stash, before throwing exception
1139
	}
1140
	if (file_exists($config['checkoutdir'].'/.git')) run_cmd("git stash pop");
1141
	if (isset($e)) throw $e;
1142
1143
	if (($cmd = config_translate('patchCmd')) && $cmd[0] != '#')
1144
	{
1145
		echo "Running $cmd\n";
1146
		run_cmd($cmd);
1147
	}
1148
	// fix permissions
1149
	echo "Fixing permissions\n";
1150
	chdir($config['egw_buildroot'].'/'.$config['aliasdir']);
1151
	run_cmd('/bin/chmod -R a-x,u=rwX,g=rX,o=rX .');
1152
	run_cmd('/bin/chmod +x */*cli.php phpgwapi/cron/*.php doc/rpm-build/*.php');
1153
}
1154
1155
/**
1156
 * Checkout or update EGroupware
1157
 *
1158
 * Ensures an existing checkout is from the correct branch! Otherwise it get's deleted
1159
 */
1160
function do_svncheckout()
1161
{
1162
	global $config,$svn;
1163
1164
	echo "Starting svn checkout/update\n";
1165
	if (!file_exists($config['checkoutdir']))
1166
	{
1167
		mkdir($config['checkoutdir'],0755,true);
1168
	}
1169 View Code Duplication
	elseif (!is_dir($config['checkoutdir']) || !is_writable($config['checkoutdir']))
1170
	{
1171
		throw new Exception("svn checkout directory '{$config['checkoutdir']} exists and is NO directory or NOT writable!");
1172
	}
1173
	chdir($config['checkoutdir']);
1174
1175
	// do we use a just created tag --> list of taged modules
1176
	if ($config['svntag'])
1177
	{
1178
		if (!isset($config['modules']))
1179
		{
1180
			get_modules_per_repo();
1181
		}
1182
		$config['svntag'] = config_translate('svntag');	// in case svntag command did not run, translate tag name
1183
1184
		if (file_exists($config['aliasdir']))
1185
		{
1186
			system('/bin/rm -rf .svn '.$config['aliasdir']);	// --> remove the whole checkout, as we dont implement switching tags
1187
			clearstatcache();
1188
		}
1189
		foreach($config['modules'] as $repo => $modules)
1190
		{
1191
			$cmd = $svn.' co ';
1192
			foreach($modules as $path => $url)
1193
			{
1194
				if ($path == $config['aliasdir'])
1195
				{
1196
					$cmd = $svn.' co '.$repo.'/'.$config['svntag'].'/'.$path;
1197
					run_cmd($cmd);
1198
					chdir($path);
1199
					$cmd = $svn.' co ';
1200
					continue;
1201
				}
1202
				if(file_exists($config['aliasdir']))
1203
				{
1204
					die("'egroupware' applications must be first one in externals!\n");
1205
				}
1206
				$cmd .= ' '.$repo.'/'.$config['svntag'].'/'.basename($path);
1207
			}
1208
			run_cmd($cmd);
1209
		}
1210
	}
1211
	// regular branch update, without tag
1212
	else
1213
	{
1214
		$svnbranch = $config['svnbase'].'/'.$config['svnbranch'];
1215
		if (file_exists($config['aliasdir']))
1216
		{
1217
			// check if correct branch
1218
			$cmd = 'LANG=C '.$svn.' info';
1219
			$output = $ret = null;
1220
			exec($cmd,$output,$ret);
1221
			foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1222
			{
1223
				if ($ret || substr($line,0,5) == 'URL: ')
1224
				{
1225
					$url = substr($line,5);
1226
					if ($ret || substr($url,0,strlen($svnbranch)) != $svnbranch)	// wrong branch (or no svn dir)
1227
					{
1228
						echo "Checkout is of wrong branch --> deleting it\n";
1229
						system('/bin/rm -rf .svn '.$config['aliasdir']);	// --> remove the whole checkout
1230
						clearstatcache();
1231
					}
1232
					break;
1233
				}
1234
			}
1235
		}
1236
		$url = $svnbranch.'/'.$config['svnalias'];
1237
		$cmd = $svn.' co '.$url.' .';
1238
		run_cmd($cmd);
1239
1240
		chdir($config['aliasdir']);
1241
		foreach($config['extra'] as $module)
1242
		{
1243
			$module = config_translate(null, $module);	// allow to use config vars like $svnbranch in module
1244
			$url = strpos($module,'://') === false ? $svnbranch.'/' : '';
1245
			$url .= $module;
1246
			$cmd = $svn.' co '.$url;
1247
			run_cmd($cmd);
1248
		}
1249
	}
1250
	// do composer install to fetch dependencies
1251
	if ($config['composer'])
1252
	{
1253
		run_cmd($config['composer'].' install --ignore-platform-reqs --no-dev');
1254
	}
1255
	// run after-checkout command(s), eg. to purge source directories
1256
	run_cmd($config['after-checkout']);
1257
}
1258
1259
/**
1260
 * Get module path per svn repo from our config
1261
 *
1262
 * @return array with $repro_url => $path => $url, eg. array(
1263
 *		"svn+ssh://[email protected]/egroupware" => array(
1264
 *			"egroupware" => "svn+ssh://[email protected]/egroupware/branches/14.2/egroupware",
1265
 *			"egroupware/addressbook" => "svn+ssh://[email protected]/egroupware/branches/14.2/addressbook",
1266
 */
1267
function get_modules_per_svn_repo()
1268
{
1269
	global $config,$svn,$verbose;
1270
1271
	// process alias/externals
1272
	$svnbranch = $config['svnbase'].'/'.$config['svnbranch'];
1273
	$url = $svnbranch.'/'.$config['svnalias'];
1274
	$cmd = $svn.' propget svn:externals --strict '.$url;
1275
	if ($verbose) echo $cmd."\n";
1276
	$output = $ret = null;
1277
	exec($cmd,$output,$ret);
1278
	$config['modules'] = array();
1279
	foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1280
	{
1281
		$line = trim($line);
1282
		if (empty($line) || $line[0] == '#') continue;
1283
		list($path,$url) = preg_split('/[ \t\r\n]+/',$line);
1284
		$matches = null;
1285
		if (!preg_match('/([a-z+]+:\/\/[a-z@.]+\/[a-z]+)\/(branches|tags|trunk)/',$url,$matches)) die("Invalid SVN URL: $url\n");
1286
		$repo = $matches[1];
1287
		if ($repo == 'http://svn.egroupware.org/egroupware') $repo = 'svn+ssh://[email protected]/egroupware';
1288
		$config['modules'][$repo][$path] = $url;
1289
	}
1290
	// process extra modules
1291
	foreach($config['extra'] as $module)
1292
	{
1293
		$module = config_translate(null, $module);	// allow to use config vars like $svnbranch in module
1294
		$url = strpos($module,'://') === false ? $svnbranch.'/' : '';
1295
		$url .= $module;
1296
		if (strpos($module,'://') !== false) $module = basename($module);
1297
		if (!preg_match('/([a-z+]+:\/\/[a-z@.]+\/[a-z]+)\/(branches|tags|trunk)/',$url,$matches)) die("Invalid SVN URL: $url\n");
1298
		$repo = $matches[1];
1299
		if ($repo == 'http://svn.egroupware.org/egroupware') $repo = 'svn+ssh://[email protected]/egroupware';
1300
		$config['modules'][$repo][$config['aliasdir'].'/'.$module] = $url;
1301
	}
1302
	if ($verbose) print_r($config['modules']);
1303
	return $config['modules'];
1304
}
1305
1306
/**
1307
 * Create svn tag or branch
1308
 */
1309
function do_svntag()
1310
{
1311
	global $config,$svn;
1312
1313
	if (empty($config['svntag'])) return;	// otherwise we copy everything in svn root!
1314
1315
	$config['svntag'] = config_translate('svntag');	// allow to use config vars like $version in tag
1316
1317
	echo "Creating SVN tag $config[svntag]\n";
1318
	if (!isset($config['modules']))
1319
	{
1320
		get_modules_per_repo();
1321
	}
1322
	// create tags (per repo)
1323
	foreach($config['modules'] as $repo => $modules)
0 ignored issues
show
Bug introduced by
The expression $config['modules'] of type null is not traversable.
Loading history...
1324
	{
1325
		$cmd = $svn.' cp --parents -m '.escapeshellarg('Creating '.$config['svntag']).' '.implode(' ',$modules).' '.$repo.'/'.$config['svntag'].'/';
1326
		run_cmd($cmd);
1327
	}
1328
}
1329
1330
/**
1331
 * Runs given shell command
1332
 *
1333
 * If command return non-zero exit-code:
1334
 * 1) output is echoed, if not already running verbose
1335
 * 2a) if exit-code is contained in $no_bailout --> return it
1336
 * 2b) otherwise throws with $cmd as message and exit-code
1337
 *
1338
 * @param string $cmd
1339
 * @param array& $output=null $output of command, only if !$verbose !!!
1340
 * @param int|array $no_bailout =null exit code(s) to NOT bail out
1341
 * @throws Exception on non-zero exit-code not matching $no_bailout
1342
 * @return int exit code of $cmd
1343
 */
1344
function run_cmd($cmd,array &$output=null,$no_bailout=null)
1345
{
1346
	global $verbose;
1347
1348
	if ($verbose && func_num_args() == 1)
1349
	{
1350
		echo $cmd."\n";
1351
		$ret = null;
1352
		system($cmd,$ret);
1353
	}
1354
	else
1355
	{
1356
		$output[] = $cmd;
1357
		exec($cmd,$output,$ret);
1358
		if ($verbose) echo implode("\n",$output)."\n";
1359
	}
1360
	if ($ret && !in_array($ret,(array)$no_bailout))
1361
	{
1362
		if (!$verbose) echo implode("\n",$output)."\n";
1363
		throw new Exception("Error during '$cmd' --> aborting",$ret);
1364
	}
1365
	return $ret;
1366
}
1367
1368
/**
1369
 * Format array or other types as (one-line) string, eg. for error_log statements
1370
 *
1371
 * @param mixed $var variable to dump
1372
 * @return string
1373
 */
1374 View Code Duplication
function array2string($var)
0 ignored issues
show
Best Practice introduced by
The function array2string() has been defined more than once; this definition is ignored, only the first definition in api/src/loader/common.php (L223-242) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1375
{
1376
	switch (($type = gettype($var)))
1377
	{
1378
		case 'boolean':
1379
			return $var ? 'TRUE' : 'FALSE';
1380
		case 'string':
1381
			return "'$var'";
1382
		case 'integer':
1383
		case 'double':
1384
		case 'resource':
1385
			return $var;
1386
		case 'NULL':
1387
			return 'NULL';
1388
		case 'object':
1389
		case 'array':
1390
			return str_replace(array("\n",'    '/*,'Array'*/),'',print_r($var,true));
1391
	}
1392
	return 'UNKNOWN TYPE!';
1393
}
1394
1395
/**
1396
 * Give usage information and an optional error-message, before stoping program execution with exit-code 90 or 0
1397
 *
1398
 * @param string $error =null optional error-message
1399
 */
1400
function usage($error=null)
0 ignored issues
show
Best Practice introduced by
The function usage() has been defined more than once; this definition is ignored, only the first definition in admin/admin-cli.php (L311-349) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1401
{
1402
	global $prog,$config,$verbose;
1403
1404
	echo "Usage: $prog [-h|--help] [-v|--verbose] [options, ...]\n\n";
1405
	echo "options and their defaults:\n";
1406
	if ($verbose)
1407
	{
1408
		if (!isset($config['modules'])) $config['modules'] = get_modules_per_repo();
1409
	}
1410
	else
1411
	{
1412
		unset($config['modules']);	// they give an error, because of nested array and are quite lengthy
1413
	}
1414
	foreach($config as $name => $default)
1415
	{
1416
		if (is_array($default)) $default = json_encode ($default, JSON_UNESCAPED_SLASHES);
1417
		echo '--'.str_pad($name,20).$default."\n";
1418
	}
1419
	if ($error)
1420
	{
1421
		echo "$error\n\n";
1422
		exit(90);
1423
	}
1424
	exit(0);
1425
}
1426