nixenvironment 0.0.59 → 0.0.60
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +14 -14
- data/bin/Config +2 -2
- data/bin/nixenvironment +420 -842
- data/legacy/CleanWorkingCopy.sh +69 -0
- data/legacy/Deploy.sh +44 -0
- data/legacy/DeployAPK.py +58 -0
- data/legacy/DeployIPA.sh +125 -0
- data/legacy/DetectSCM.sh +11 -0
- data/legacy/GenerateCodeCoverageForXCTests.sh +134 -0
- data/legacy/GenerateCodeDuplicationReport.sh +24 -0
- data/legacy/IncrementBuildNumber.py +129 -0
- data/legacy/LoadBuildEnvVars.sh +116 -0
- data/legacy/MakeTag.sh +94 -0
- data/legacy/RemoveTemporaryFiles.sh +9 -0
- data/legacy/SaveRevision.sh +122 -0
- data/legacy/UnityBuildAndroid.py +84 -0
- data/legacy/UnityBuildAutomationScripts/CommandLineReader.cs +130 -0
- data/legacy/UnityBuildAutomationScripts/NIXBuilder.cs +105 -0
- data/legacy/UnityBuildEnvVars.py +41 -0
- data/legacy/VerifyBinarySigning.py +80 -0
- data/legacy/svn-clean.pl +246 -0
- data/legacy/svncopy.pl +1134 -0
- data/lib/nixenvironment.rb +5 -0
- data/lib/nixenvironment/archiver.rb +690 -0
- data/lib/nixenvironment/build_env_vars_loader.rb +24 -0
- data/lib/nixenvironment/cmd_executor.rb +33 -0
- data/lib/nixenvironment/config.rb +127 -14
- data/lib/nixenvironment/git.rb +107 -0
- data/lib/nixenvironment/plist.rb +52 -0
- data/lib/nixenvironment/version.rb +1 -1
- data/lib/nixenvironment/xcodebuild.rb +167 -0
- data/nixenvironment.gemspec +6 -0
- data/utils/XcodeIconTagger/IconTagger +0 -0
- data/utils/XcodeIconTagger/masks/OneLineMask.png +0 -0
- data/utils/XcodeIconTagger/masks/TwoLineMask.png +0 -0
- data/utils/aapt +0 -0
- data/utils/gcovr +986 -0
- data/utils/identitieslist +0 -0
- data/utils/simian-2.3.33.jar +0 -0
- metadata +118 -2
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
|
3
|
+
# Created by Egor Zubkov on 16/12/14.
|
4
|
+
# Copyright 2014 NIX. All rights reserved.
|
5
|
+
|
6
|
+
import argparse
|
7
|
+
import os
|
8
|
+
import sys
|
9
|
+
import UnityBuildEnvVars
|
10
|
+
|
11
|
+
default_configuration = "Debug"
|
12
|
+
|
13
|
+
def main():
|
14
|
+
parser = argparse.ArgumentParser(description="Script builds Android build in Unity project.")
|
15
|
+
parser.add_argument("--configuration", default=default_configuration)
|
16
|
+
parser.add_argument("--unity-path", required=True)
|
17
|
+
parser.add_argument("--development-build", action="store_true")
|
18
|
+
parser.add_argument("--keystore-path")
|
19
|
+
parser.add_argument("--keystore-password")
|
20
|
+
parser.add_argument("--key-alias-name")
|
21
|
+
parser.add_argument("--key-alias-password")
|
22
|
+
args = parser.parse_args()
|
23
|
+
|
24
|
+
if not args.unity_path:
|
25
|
+
print "ERROR: unity_path must not be empty string!"
|
26
|
+
exit(1)
|
27
|
+
|
28
|
+
development_build_arg = args.development_build
|
29
|
+
configuration_arg = args.configuration
|
30
|
+
|
31
|
+
if configuration_arg != default_configuration:
|
32
|
+
configuration_arg = "Release"
|
33
|
+
development_build_arg = None
|
34
|
+
|
35
|
+
if not args.keystore_path or not os.path.exists(args.keystore_path):
|
36
|
+
print "ERROR: KeyStore file must exist!"
|
37
|
+
exit(1)
|
38
|
+
|
39
|
+
revision_number = UnityBuildEnvVars.revision_number()
|
40
|
+
|
41
|
+
build_path = build_apk(revision_number, args.unity_path, development_build_arg, args.keystore_path, args.keystore_password, args.key_alias_name, args.key_alias_password)
|
42
|
+
UnityBuildEnvVars.save_env_vars(configuration_arg, build_path)
|
43
|
+
|
44
|
+
def build_apk(bundle_version, unity_path, development_build, keystore_path, keystore_password, key_alias_name, key_alias_password):
|
45
|
+
build_path = os.path.join(os.getcwd(), "Builds/Android/Build.apk")
|
46
|
+
build_path_arg = "buildPath=%s" % build_path
|
47
|
+
bundle_version_arg = ";bundleVersionCode=%s" % bundle_version
|
48
|
+
|
49
|
+
development_build_arg = ""
|
50
|
+
|
51
|
+
if development_build == True:
|
52
|
+
development_build_arg = ";developmentBuild="
|
53
|
+
|
54
|
+
keystore_path_arg = ""
|
55
|
+
|
56
|
+
if keystore_path:
|
57
|
+
keystore_path_arg = ";keyStorePath=%s" % keystore_path
|
58
|
+
|
59
|
+
keystore_password_arg = ""
|
60
|
+
|
61
|
+
if keystore_password:
|
62
|
+
keystore_password_arg = ";keyStorePassword=%s" % keystore_password
|
63
|
+
|
64
|
+
key_alias_name_arg = ""
|
65
|
+
|
66
|
+
if key_alias_name:
|
67
|
+
key_alias_name_arg = ";keyAliasName=%s" % key_alias_name
|
68
|
+
|
69
|
+
key_alias_password_arg = ""
|
70
|
+
|
71
|
+
if key_alias_password:
|
72
|
+
key_alias_password_arg = ";keyAliasPassword=%s" % key_alias_password
|
73
|
+
|
74
|
+
args = "'{0}' -projectPath '{1}' -executeMethod NIXBuilder.MakeAndroidBuild -batchmode -logFile -quit -customArgs:\"{2}{3}{4}{5}{6}{7}{8}\"".format(unity_path, os.getcwd(), build_path_arg, bundle_version_arg, development_build_arg, keystore_path_arg, keystore_password_arg, key_alias_name_arg, key_alias_password_arg)
|
75
|
+
success = os.system(args)
|
76
|
+
|
77
|
+
if success != 0:
|
78
|
+
print "ERROR: build_apk failed!"
|
79
|
+
exit(1)
|
80
|
+
|
81
|
+
return build_path
|
82
|
+
|
83
|
+
if __name__ == '__main__':
|
84
|
+
sys.exit(main())
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#region Author
|
2
|
+
/************************************************************************************************************
|
3
|
+
Author: EpixCode (Keven Poulin)
|
4
|
+
Website: http://www.EpixCode.com
|
5
|
+
GitHub: https://github.com/EpixCode
|
6
|
+
Twitter: https://twitter.com/EpixCode (@EpixCode)
|
7
|
+
LinkedIn: http://www.linkedin.com/in/kevenpoulin
|
8
|
+
************************************************************************************************************/
|
9
|
+
#endregion
|
10
|
+
|
11
|
+
#region Copyright
|
12
|
+
/************************************************************************************************************
|
13
|
+
Copyright (C) 2013 EpixCode
|
14
|
+
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
16
|
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
17
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
18
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
19
|
+
to do so, subject to the following conditions:
|
20
|
+
|
21
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
22
|
+
portions of the Software.
|
23
|
+
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
25
|
+
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
26
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
27
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
|
+
************************************************************************************************************/
|
30
|
+
#endregion
|
31
|
+
|
32
|
+
#region Class Documentation
|
33
|
+
/************************************************************************************************************
|
34
|
+
Class Name: CommandLineReader.cs
|
35
|
+
Namespace: Com.EpixCode.Util
|
36
|
+
Type: Util, Static
|
37
|
+
Definition:
|
38
|
+
CommandLineReader.cs give the ability to access [Custom Arguments] sent
|
39
|
+
through the command line. Simply add your custom arguments under the
|
40
|
+
keyword '-CustomArgs:' and seperate them by ';'.
|
41
|
+
Example:
|
42
|
+
C:\Program Files (x86)\Unity\Editor\Unity.exe [ProjectLocation] -executeMethod [Your entrypoint] -quit -CustomArgs:Language=en_US;Version=1.02
|
43
|
+
|
44
|
+
************************************************************************************************************/
|
45
|
+
#endregion
|
46
|
+
|
47
|
+
#region Using
|
48
|
+
using System;
|
49
|
+
using System.Collections.Generic;
|
50
|
+
using System.Linq;
|
51
|
+
using UnityEngine;
|
52
|
+
#endregion
|
53
|
+
|
54
|
+
public class CommandLineReader
|
55
|
+
{
|
56
|
+
//Config
|
57
|
+
private const string CUSTOM_ARGS_PREFIX = "-customArgs:";
|
58
|
+
private const char CUSTOM_ARGS_SEPARATOR = ';';
|
59
|
+
|
60
|
+
public static string[] GetCommandLineArgs()
|
61
|
+
{
|
62
|
+
return Environment.GetCommandLineArgs();
|
63
|
+
}
|
64
|
+
|
65
|
+
public static string GetCommandLine()
|
66
|
+
{
|
67
|
+
string[] args = GetCommandLineArgs();
|
68
|
+
|
69
|
+
if (args.Length > 0)
|
70
|
+
{
|
71
|
+
return string.Join(" ", args);
|
72
|
+
}
|
73
|
+
else
|
74
|
+
{
|
75
|
+
Debug.LogError("CommandLineReader.cs - GetCommandLine() - Can't find any command line arguments!");
|
76
|
+
return "";
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
public static Dictionary<string,string> GetCustomArguments()
|
81
|
+
{
|
82
|
+
Dictionary<string, string> customArgsDict = new Dictionary<string, string>();
|
83
|
+
string[] commandLineArgs = GetCommandLineArgs();
|
84
|
+
string[] customArgs;
|
85
|
+
string[] customArgBuffer;
|
86
|
+
string customArgsStr = "";
|
87
|
+
|
88
|
+
try
|
89
|
+
{
|
90
|
+
customArgsStr = commandLineArgs.Where(row => row.Contains(CUSTOM_ARGS_PREFIX)).Single();
|
91
|
+
}
|
92
|
+
catch (Exception e)
|
93
|
+
{
|
94
|
+
Debug.LogError("CommandLineReader.cs - GetCustomArguments() - Can't retrieve any custom arguments in the command line [" + commandLineArgs + "]. Exception: " + e);
|
95
|
+
return customArgsDict;
|
96
|
+
}
|
97
|
+
|
98
|
+
customArgsStr = customArgsStr.Replace(CUSTOM_ARGS_PREFIX, "");
|
99
|
+
customArgs = customArgsStr.Split(CUSTOM_ARGS_SEPARATOR);
|
100
|
+
|
101
|
+
foreach (string customArg in customArgs)
|
102
|
+
{
|
103
|
+
customArgBuffer = customArg.Split('=');
|
104
|
+
if (customArgBuffer.Length == 2)
|
105
|
+
{
|
106
|
+
customArgsDict.Add(customArgBuffer[0], customArgBuffer[1]);
|
107
|
+
}
|
108
|
+
else
|
109
|
+
{
|
110
|
+
Debug.LogWarning("CommandLineReader.cs - GetCustomArguments() - The custom argument [" + customArg + "] seem to be malformed.");
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
return customArgsDict;
|
115
|
+
}
|
116
|
+
|
117
|
+
public static string GetCustomArgument(string argumentName)
|
118
|
+
{
|
119
|
+
Dictionary<string, string> customArgsDict = GetCustomArguments();
|
120
|
+
|
121
|
+
if (customArgsDict.ContainsKey(argumentName))
|
122
|
+
{
|
123
|
+
return customArgsDict[argumentName];
|
124
|
+
}
|
125
|
+
else
|
126
|
+
{
|
127
|
+
return null;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
/*
|
2
|
+
NIXBuilder.cs
|
3
|
+
Automatically changes the target platform and creates a build.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
Place in an Editor folder.
|
7
|
+
*/
|
8
|
+
|
9
|
+
using System;
|
10
|
+
using System.IO;
|
11
|
+
using UnityEditor;
|
12
|
+
|
13
|
+
public static class NIXBuilder
|
14
|
+
{
|
15
|
+
private const string BundleVersionCodeArgument = "bundleVersionCode";
|
16
|
+
|
17
|
+
private const string BuildPathArgument = "buildPath";
|
18
|
+
|
19
|
+
private const string DevelopmentBuildArgument = "developmentBuild";
|
20
|
+
|
21
|
+
private const string KeyStorePathArgument = "keyStorePath";
|
22
|
+
|
23
|
+
private const string KeyStorePasswordArgument = "keyStorePassword";
|
24
|
+
|
25
|
+
private const string KeyAliasNameArgument = "keyAliasName";
|
26
|
+
|
27
|
+
private const string KeyAliasPasswordArgument = "keyAliasPassword";
|
28
|
+
|
29
|
+
public static string[] GetScenePaths()
|
30
|
+
{
|
31
|
+
string[] scenes = new string[EditorBuildSettings.scenes.Length];
|
32
|
+
|
33
|
+
for (int i = 0; i < scenes.Length; i++)
|
34
|
+
{
|
35
|
+
scenes[i] = EditorBuildSettings.scenes[i].path;
|
36
|
+
}
|
37
|
+
|
38
|
+
return scenes;
|
39
|
+
}
|
40
|
+
|
41
|
+
public static void MakeiOSBuild()
|
42
|
+
{
|
43
|
+
string projectPath = CommandLineReader.GetCustomArgument(BuildPathArgument);
|
44
|
+
CreateFolder(projectPath);
|
45
|
+
|
46
|
+
BuildTarget buildTarget;
|
47
|
+
|
48
|
+
#if UNITY_4_6
|
49
|
+
buildTarget = BuildTarget.iPhone;
|
50
|
+
#else
|
51
|
+
buildTarget = BuildTarget.iOS;
|
52
|
+
#endif
|
53
|
+
|
54
|
+
EditorUserBuildSettings.SwitchActiveBuildTarget(buildTarget);
|
55
|
+
BuildPipeline.BuildPlayer(GetScenePaths(), projectPath, buildTarget, GetBuildOptions());
|
56
|
+
}
|
57
|
+
|
58
|
+
public static void MakeAndroidBuild()
|
59
|
+
{
|
60
|
+
string buildPath = CommandLineReader.GetCustomArgument(BuildPathArgument);
|
61
|
+
CreateFolder(Path.GetDirectoryName(buildPath));
|
62
|
+
|
63
|
+
var keyStorePath = CommandLineReader.GetCustomArgument(KeyStorePathArgument);
|
64
|
+
|
65
|
+
PlayerSettings.Android.bundleVersionCode = Convert.ToInt32(CommandLineReader.GetCustomArgument(BundleVersionCodeArgument));
|
66
|
+
|
67
|
+
if (File.Exists(keyStorePath))
|
68
|
+
{
|
69
|
+
PlayerSettings.Android.keystoreName = keyStorePath;
|
70
|
+
PlayerSettings.Android.keystorePass = CommandLineReader.GetCustomArgument(KeyStorePasswordArgument);
|
71
|
+
PlayerSettings.Android.keyaliasName = CommandLineReader.GetCustomArgument(KeyAliasNameArgument);
|
72
|
+
PlayerSettings.Android.keyaliasPass = CommandLineReader.GetCustomArgument(KeyAliasPasswordArgument);
|
73
|
+
}
|
74
|
+
else
|
75
|
+
{
|
76
|
+
PlayerSettings.Android.keystoreName = null;
|
77
|
+
PlayerSettings.Android.keystorePass = null;
|
78
|
+
PlayerSettings.Android.keyaliasName = null;
|
79
|
+
PlayerSettings.Android.keyaliasPass = null;
|
80
|
+
}
|
81
|
+
|
82
|
+
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
|
83
|
+
BuildPipeline.BuildPlayer(GetScenePaths(), buildPath, BuildTarget.Android, GetBuildOptions());
|
84
|
+
}
|
85
|
+
|
86
|
+
public static void CreateFolder(string thePath)
|
87
|
+
{
|
88
|
+
if (!Directory.Exists(thePath))
|
89
|
+
{
|
90
|
+
Directory.CreateDirectory(thePath);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
public static BuildOptions GetBuildOptions()
|
95
|
+
{
|
96
|
+
BuildOptions buildOptions = BuildOptions.None;
|
97
|
+
|
98
|
+
if (CommandLineReader.GetCustomArgument(DevelopmentBuildArgument) != null)
|
99
|
+
{
|
100
|
+
buildOptions |= BuildOptions.Development;
|
101
|
+
}
|
102
|
+
|
103
|
+
return buildOptions;
|
104
|
+
}
|
105
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
|
3
|
+
# Created by Egor Zubkov on 17/12/14.
|
4
|
+
# Copyright 2014 NIX. All rights reserved.
|
5
|
+
|
6
|
+
import os
|
7
|
+
import plistlib
|
8
|
+
|
9
|
+
last_revision_filename = "_last_revision.sh"
|
10
|
+
unity_last_build_vars_filename = "_unity_last_build_vars.plist"
|
11
|
+
|
12
|
+
monotomic_revision_var = "$MONOTONIC_REVISION"
|
13
|
+
working_copy_is_clean_var = "$WORKING_COPY_IS_CLEAN"
|
14
|
+
|
15
|
+
configuration_key = "Configuration"
|
16
|
+
revision_number_key = "RevisionNumber"
|
17
|
+
build_path_key = "BuildPath"
|
18
|
+
|
19
|
+
|
20
|
+
def revision_number():
|
21
|
+
command = os.popen("source %s && echo %s" % (last_revision_filename, monotomic_revision_var))
|
22
|
+
revision_number = command.read().strip()
|
23
|
+
|
24
|
+
if revision_number == '':
|
25
|
+
exit(1)
|
26
|
+
|
27
|
+
return revision_number
|
28
|
+
|
29
|
+
|
30
|
+
def save_env_vars(configuration, build_path):
|
31
|
+
plist = { configuration_key : configuration, revision_number_key : revision_number(), build_path_key : build_path }
|
32
|
+
|
33
|
+
last_build_vars = os.path.join(os.getcwd(), unity_last_build_vars_filename)
|
34
|
+
plistlib.writePlist(plist, last_build_vars)
|
35
|
+
|
36
|
+
|
37
|
+
def env_vars():
|
38
|
+
last_build_vars_path = os.path.join(os.getcwd(), unity_last_build_vars_filename)
|
39
|
+
last_build_vars = plistlib.readPlist(last_build_vars_path)
|
40
|
+
return last_build_vars[configuration_key], last_build_vars[revision_number_key], last_build_vars[build_path_key]
|
41
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
|
3
|
+
import os
|
4
|
+
import optparse
|
5
|
+
import sys
|
6
|
+
|
7
|
+
def main():
|
8
|
+
parser = optparse.OptionParser(description='Verifies that iOS binary has expected keychain-access-group. Using the same keychain-access-group insures\
|
9
|
+
that app will have access to its keychain after updates.',
|
10
|
+
prog='VerifyBinarySigning')
|
11
|
+
|
12
|
+
parser.add_option("-b", "--binary", dest="path_to_binary", help="Full path to app binary", metavar="BINARY")
|
13
|
+
parser.add_option("-s", "--keychain_access_group", dest="keychain_access_group", help="Expected keychain-access-group", metavar="KEYCHAIN-ACCESS-GROUP")
|
14
|
+
|
15
|
+
(options, args) = parser.parse_args()
|
16
|
+
|
17
|
+
if options.path_to_binary is None or options.keychain_access_group is None:
|
18
|
+
parser.error('All arguments must be specified. Use option -h to see the usage.')
|
19
|
+
|
20
|
+
logfile = open(options.path_to_binary, "r").readlines()
|
21
|
+
|
22
|
+
currentLineNumber = 0
|
23
|
+
|
24
|
+
"""
|
25
|
+
example from the binary:
|
26
|
+
|
27
|
+
.
|
28
|
+
.
|
29
|
+
.
|
30
|
+
<key>keychain-access-groups</key>
|
31
|
+
<array>
|
32
|
+
<string>KEYCHAIN-ACCESS-GROUP</string>
|
33
|
+
</array>
|
34
|
+
.
|
35
|
+
.
|
36
|
+
.
|
37
|
+
"""
|
38
|
+
|
39
|
+
for line in logfile:
|
40
|
+
if line.find('keychain-access-groups') != -1:
|
41
|
+
currentLineNumber += 1
|
42
|
+
|
43
|
+
if logfile[currentLineNumber].strip() != "<array>":
|
44
|
+
print_error("Something wrong happened, there is no <array> in the <key>keychain-access-groups</key>")
|
45
|
+
return 1
|
46
|
+
|
47
|
+
currentLineNumber += 1
|
48
|
+
|
49
|
+
while logfile[currentLineNumber].strip() != "</array>":
|
50
|
+
line_with_access_group = logfile[currentLineNumber].strip().replace("<string>","").replace("</string>","")
|
51
|
+
|
52
|
+
print line_with_access_group
|
53
|
+
|
54
|
+
if line_with_access_group == options.keychain_access_group:
|
55
|
+
print 'App is signed correctly'
|
56
|
+
return 0
|
57
|
+
|
58
|
+
currentLineNumber += 1
|
59
|
+
|
60
|
+
print_error("App is signed incorrectly, specified keychain access group '%s' was not found" % options.keychain_access_group)
|
61
|
+
return 1
|
62
|
+
|
63
|
+
currentLineNumber += 1
|
64
|
+
|
65
|
+
print_error('App must be signed')
|
66
|
+
return 1
|
67
|
+
|
68
|
+
def print_error(error_message):
|
69
|
+
""" Prints error message with predefined prefix.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
error_message: Error message.
|
73
|
+
"""
|
74
|
+
|
75
|
+
XCODE_ERROR_PREFIX = 'error: ' # log messages with such prefix are highlighted in XCode as errors
|
76
|
+
|
77
|
+
print('%s%s' % (XCODE_ERROR_PREFIX, error_message))
|
78
|
+
|
79
|
+
if __name__ == '__main__':
|
80
|
+
sys.exit(main())
|
data/legacy/svn-clean.pl
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
#!/usr/bin/perl
|
2
|
+
|
3
|
+
# svn-clean - Wipes out unversioned files from SVN working copy.
|
4
|
+
# Copyright (C) 2004, 2005, 2006 Simon Perreault
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or
|
7
|
+
# modify it under the terms of the GNU General Public License
|
8
|
+
# as published by the Free Software Foundation; either version 2
|
9
|
+
# of the License, or (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
19
|
+
|
20
|
+
use strict;
|
21
|
+
use Cwd;
|
22
|
+
use File::Path;
|
23
|
+
use Getopt::Long;
|
24
|
+
use Pod::Usage;
|
25
|
+
|
26
|
+
# Try to use SVN module if available.
|
27
|
+
my $use_svn_module = eval { require SVN::Client };
|
28
|
+
|
29
|
+
my $CWD = getcwd;
|
30
|
+
|
31
|
+
my @exclude = ();
|
32
|
+
my $force = 0;
|
33
|
+
my $quiet = 0;
|
34
|
+
my $print = 0;
|
35
|
+
my $help = 0;
|
36
|
+
my $man = 0;
|
37
|
+
my $nonrecursive = 0;
|
38
|
+
my @paths = ($CWD);
|
39
|
+
GetOptions(
|
40
|
+
"exclude=s" => \@exclude,
|
41
|
+
"force" => \$force,
|
42
|
+
"non-recursive|N" => \$nonrecursive,
|
43
|
+
"quiet" => \$quiet,
|
44
|
+
"print" => \$print,
|
45
|
+
"help|?" => \$help,
|
46
|
+
"man" => \$man
|
47
|
+
) or pod2usage(2);
|
48
|
+
pod2usage(1) if $help;
|
49
|
+
pod2usage( -exitstatus => 0, -verbose => 2 ) if $man;
|
50
|
+
@paths = map { Cwd::abs_path($_) } @ARGV if @ARGV;
|
51
|
+
|
52
|
+
# Precompile regexes.
|
53
|
+
$_ = qr/$_/ foreach @exclude;
|
54
|
+
|
55
|
+
if ($use_svn_module) {
|
56
|
+
|
57
|
+
# Create SVN client object. No need for connection to remote server.
|
58
|
+
my $ctx = new SVN::Client;
|
59
|
+
|
60
|
+
# Call handler function with status info for each file.
|
61
|
+
$ctx->status( $_, undef, \&clean, !$nonrecursive, 1, 0, 1 )
|
62
|
+
for @paths;
|
63
|
+
}
|
64
|
+
else {
|
65
|
+
warn "Warning: Not using SVN Perl modules, this might be slow.\n"
|
66
|
+
unless $quiet;
|
67
|
+
|
68
|
+
# Build svn client command
|
69
|
+
my @command = qw(svn status --no-ignore -v);
|
70
|
+
if ($nonrecursive) {
|
71
|
+
push @command, '-N';
|
72
|
+
}
|
73
|
+
|
74
|
+
# Main file-wiping loop.
|
75
|
+
if ( $^O eq 'MSWin32' ) {
|
76
|
+
|
77
|
+
# Perl on Windows currently doesn't have list pipe opens.
|
78
|
+
open SVN, join( ' ', @command, @paths ) . '|'
|
79
|
+
or die "Can't call program \"svn\": $!\n";
|
80
|
+
}
|
81
|
+
else {
|
82
|
+
open SVN, "-|", @command, @paths
|
83
|
+
or die "Can't call program \"svn\": $!\n";
|
84
|
+
}
|
85
|
+
LINE: while (<SVN>) {
|
86
|
+
if (/^([\?ID])/) {
|
87
|
+
my $file = (split)[-1];
|
88
|
+
foreach my $ex (@exclude) {
|
89
|
+
if ( $file =~ $ex ) {
|
90
|
+
print "excluded $file\n" unless $quiet or $print;
|
91
|
+
next LINE;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
if ( $1 eq 'D' ) {
|
95
|
+
next unless -f $file;
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
next unless -e $file;
|
99
|
+
}
|
100
|
+
if ($print) {
|
101
|
+
print "$file\n";
|
102
|
+
}
|
103
|
+
else {
|
104
|
+
rmtree( $file, !$quiet, !$force );
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
# Main file-wiping function.
|
111
|
+
sub clean {
|
112
|
+
my ( $path, $status ) = @_;
|
113
|
+
|
114
|
+
# Use relative path for pretty-printing.
|
115
|
+
if ( $path =~ s/^\Q$CWD\E\/?//o ) {
|
116
|
+
|
117
|
+
# If the substitution succeeded, we should have a relative path. Make
|
118
|
+
# sure we don't delete critical stuff.
|
119
|
+
return if $path =~ /^\//;
|
120
|
+
}
|
121
|
+
|
122
|
+
# Find files needing to be removed.
|
123
|
+
if ( $status->text_status == $SVN::Wc::Status::unversioned
|
124
|
+
or $status->text_status == $SVN::Wc::Status::ignored
|
125
|
+
or $status->text_status == $SVN::Wc::Status::deleted )
|
126
|
+
{
|
127
|
+
foreach my $ex (@exclude) {
|
128
|
+
if ( $path =~ $ex ) {
|
129
|
+
print "excluded $path\n" unless $quiet or $print;
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
# Make sure the file exists before removing it. Do not remove deleted
|
135
|
+
# directories as they are needed to remove the files they contain when
|
136
|
+
# committing.
|
137
|
+
lstat $path or stat $path;
|
138
|
+
if (
|
139
|
+
-e _
|
140
|
+
and ( not -d _
|
141
|
+
or $status->text_status != $SVN::Wc::Status::deleted )
|
142
|
+
)
|
143
|
+
{
|
144
|
+
if ($print) {
|
145
|
+
print "$path\n";
|
146
|
+
}
|
147
|
+
else {
|
148
|
+
rmtree( $path, !$quiet, !$force );
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
__END__
|
155
|
+
|
156
|
+
=head1 NAME
|
157
|
+
|
158
|
+
svn-clean - Wipes out unversioned files from Subversion working copy
|
159
|
+
|
160
|
+
=head1 SYNOPSIS
|
161
|
+
|
162
|
+
svn-clean [options] [directory or file ...]
|
163
|
+
|
164
|
+
=head1 DESCRIPTION
|
165
|
+
|
166
|
+
B<svn-clean> will scan the given files and directories recursively and find
|
167
|
+
unversioned files and directories (files and directories that are not present in
|
168
|
+
the Subversion repository). After the scan is done, these files and directories
|
169
|
+
will be deleted.
|
170
|
+
|
171
|
+
If no file or directory is given, B<svn-clean> defaults to the current directory
|
172
|
+
(".").
|
173
|
+
|
174
|
+
B<svn-clean> uses the SVN Perl modules if they are available. This is much
|
175
|
+
faster than parsing the output of the B<svn> command-line client.
|
176
|
+
|
177
|
+
=head1 OPTIONS
|
178
|
+
|
179
|
+
=over 8
|
180
|
+
|
181
|
+
=item B<-e>, B<--exclude>
|
182
|
+
|
183
|
+
A regular expression for filenames to be exluded. For example, the following
|
184
|
+
command will skip files ending in ".zip":
|
185
|
+
|
186
|
+
=over 8
|
187
|
+
|
188
|
+
svn-clean --exclude '\.zip$'
|
189
|
+
|
190
|
+
=back
|
191
|
+
|
192
|
+
Multiple exclude patterns can be specified. If at least one matches, then the
|
193
|
+
file is skipped. For example, the following command will skip files ending in
|
194
|
+
".jpg" or ".png":
|
195
|
+
|
196
|
+
=over 8
|
197
|
+
|
198
|
+
svn-clean --exclude '\.jpg$' --exclude '\.png$'
|
199
|
+
|
200
|
+
=back
|
201
|
+
|
202
|
+
The following command will skip the entire "build" subdirectory:
|
203
|
+
|
204
|
+
=over 8
|
205
|
+
|
206
|
+
svn-clean --exclude '^build(/|$)'
|
207
|
+
|
208
|
+
=back
|
209
|
+
|
210
|
+
=item B<-f>, B<--force>
|
211
|
+
|
212
|
+
Files to which you do not have delete access (if running under VMS) or write
|
213
|
+
access (if running under another OS) will not be deleted unless you use this
|
214
|
+
option.
|
215
|
+
|
216
|
+
=item B<-N>, B<--non-recursive>
|
217
|
+
|
218
|
+
Do not search recursively for unversioned files and directories. Unversioned
|
219
|
+
directories will still be deleted along with all their contents.
|
220
|
+
|
221
|
+
=item B<-q>, B<--quiet>
|
222
|
+
|
223
|
+
Do not print progress info. In particular, do not print a message each time a
|
224
|
+
file is examined, giving the name of the file, and indicating whether "rmdir" or
|
225
|
+
"unlink" is used to remove it, or that it's skipped.
|
226
|
+
|
227
|
+
=item B<-p>, B<--print>
|
228
|
+
|
229
|
+
Do not delete anything. Instead, print the name of every file and directory that
|
230
|
+
would have been deleted.
|
231
|
+
|
232
|
+
=item B<-?>, B<-h>, B<--help>
|
233
|
+
|
234
|
+
Prints a brief help message and exits.
|
235
|
+
|
236
|
+
=item B<--man>
|
237
|
+
|
238
|
+
Prints the manual page and exits.
|
239
|
+
|
240
|
+
=back
|
241
|
+
|
242
|
+
=head1 AUTHOR
|
243
|
+
|
244
|
+
Simon Perreault <nomis80@nomis80.org>
|
245
|
+
|
246
|
+
=cut
|