|
| 1 | +import java.util.zip.ZipOutputStream |
| 2 | +import java.util.zip.ZipEntry |
| 3 | + |
| 4 | +// Custom Exception for undefined variable |
| 5 | +public class VariableNotFoundException extends Exception { |
| 6 | + public VariableNotFoundException(String errorMessage) { |
| 7 | + super(errorMessage); |
| 8 | + } |
| 9 | +} |
| 10 | +// Constants |
| 11 | +final INSTABUG_APP_TOKEN = 'INSTABUG_APP_TOKEN' |
| 12 | +final INSTABUG_API_KEY = 'INSTABUG_API_KEY' |
| 13 | +final UPLOAD_URL = 'https://api.instabug.com/api/web/public/flutter-symbol-files/android' |
| 14 | +final INSTABUG_AUTO_UPLOAD_ENABLE = 'INSTABUG_AUTO_UPLOAD_ENABLE' |
| 15 | +ext.boundary = 'abcd' + Long.toString(System.currentTimeMillis()) * 2 + 'dcba' |
| 16 | +ext.lineEnd = '\r\n' |
| 17 | + |
| 18 | +/** |
| 19 | + * task to upload symbols to Instabug endpoint |
| 20 | + */ |
| 21 | +task upload_symbols_task { |
| 22 | + doLast { |
| 23 | + try { |
| 24 | + // If obfuscate flag not provided, exit the task |
| 25 | + if (project['dart-obfuscation'] != 'true') { |
| 26 | + println "Instabug: obfuscate flag not used — skipping debug symbols upload" |
| 27 | + return |
| 28 | + } |
| 29 | + // Load local properties |
| 30 | + final localProperties = load_local_properties() |
| 31 | + |
| 32 | + // If auto upload is disabled, exit the task |
| 33 | + final uploadFlag = get_variable(INSTABUG_AUTO_UPLOAD_ENABLE, localProperties) |
| 34 | + println "$INSTABUG_AUTO_UPLOAD_ENABLE: $uploadFlag" |
| 35 | + if (!uploadFlag?.toBoolean()) { |
| 36 | + println "Instabug: skipping debug symbols upload" |
| 37 | + return |
| 38 | + } |
| 39 | + |
| 40 | + // Get upload configs |
| 41 | + final applicationToken = get_variable(INSTABUG_APP_TOKEN, localProperties); |
| 42 | + final apiKey = get_variable(INSTABUG_API_KEY, localProperties); |
| 43 | + final symbolsPath = get_symbols_path() |
| 44 | + |
| 45 | + // Prepare zip file with symbols |
| 46 | + final zipFile = prepare_zip_file(symbolsPath) |
| 47 | + |
| 48 | + upload_symbols(zipFile, applicationToken, apiKey, UPLOAD_URL) |
| 49 | + } catch (VariableNotFoundException e) { |
| 50 | + println "Instabug: uploading symbols: ${e.message}" |
| 51 | + } catch (Exception e) { |
| 52 | + println "Instabug err: uploading symbols: ${e.message}" |
| 53 | + } |
| 54 | + } |
| 55 | +} |
| 56 | +/** |
| 57 | + * load local properties into a global variable localProperties |
| 58 | + */ |
| 59 | +def load_local_properties() { |
| 60 | + // Get local properties |
| 61 | + final properties = new Properties() |
| 62 | + final propertiesFile = project.rootProject.file('local.properties') |
| 63 | + if (propertiesFile.exists()) { |
| 64 | + properties.load(propertiesFile.newDataInputStream()) |
| 65 | + } |
| 66 | + return properties |
| 67 | +} |
| 68 | +/** |
| 69 | + * get environment variable |
| 70 | + * @param name variable name |
| 71 | + * @param localProperties local properties object |
| 72 | + * @return variable environment variable for the given name |
| 73 | + */ |
| 74 | +String get_variable(name, localProperties) { |
| 75 | + // Look for the variable in local properties, gradle properties and env variables |
| 76 | + final variable = localProperties.getProperty(name) ?: project.findProperty(name) ?: System.getenv(name) |
| 77 | + if (!variable) { |
| 78 | + throw new VariableNotFoundException("$name not found. Make sure you've added the environment variable $name") |
| 79 | + } |
| 80 | + println "Instabug: ${name} found" |
| 81 | + return variable; |
| 82 | +} |
| 83 | + |
| 84 | +/** |
| 85 | + * get symbols path |
| 86 | + * @return symbolsPath the path of the symbols directory |
| 87 | + */ |
| 88 | +String get_symbols_path() { |
| 89 | + // Get root host directory path |
| 90 | + final rootFile = new File(project['rootDir'].toString()) |
| 91 | + |
| 92 | + // Get the path of the symbols directory |
| 93 | + final obfuscationPath = project['split-debug-info'] |
| 94 | + final obfuscationFile = new File(obfuscationPath) |
| 95 | + |
| 96 | + // If the provided path is relative |
| 97 | + // append the symbols directory to the root project path to get the full path |
| 98 | + if (obfuscationFile.absolute) { |
| 99 | + return obfuscationPath |
| 100 | + } else { |
| 101 | + return new File(rootFile.parent, obfuscationPath) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +/** |
| 106 | + * search for the symbols files in the symbols directory |
| 107 | + * and create a zip file with the symbols files |
| 108 | + * @param symbolsPath the path of the symbols directory |
| 109 | + * @return zipFile the zip file created |
| 110 | + */ |
| 111 | + |
| 112 | +File prepare_zip_file(symbolsPath) { |
| 113 | + // Check if the symbols directory provided exists |
| 114 | + final symbolsDir = new File(symbolsPath) |
| 115 | + if (!symbolsDir.exists()) { |
| 116 | + throw new Exception('Symbols directory not found') |
| 117 | + } |
| 118 | + |
| 119 | + // Search in symbolsPath for files with extension .symbols and add them to a list |
| 120 | + final symbolsFiles = symbolsDir.listFiles().findAll { file -> file.path.endsWith('.symbols') } |
| 121 | + if (symbolsFiles.empty) { |
| 122 | + throw new Exception('No symbols files found') |
| 123 | + } |
| 124 | + println "Instabug: ${symbolsFiles.size()} Symbols files found:" |
| 125 | + |
| 126 | + // Print list of symbols files |
| 127 | + symbolsFiles.forEach { file -> |
| 128 | + println "${file.path}" |
| 129 | + } |
| 130 | + |
| 131 | + // Create a zip file with the symbols files |
| 132 | + final zipFile = zip_files(symbolsPath, symbolsFiles) |
| 133 | + |
| 134 | + return zipFile |
| 135 | +} |
| 136 | +/** |
| 137 | + * create a zip file with the symbols files |
| 138 | + * |
| 139 | + * @param symbolsPath the path of the symbols directory |
| 140 | + * @param symbolsFiles the list of symbols files |
| 141 | + * @return zipFile the zip file created |
| 142 | + */ |
| 143 | + |
| 144 | +File zip_files(symbolsPath, symbolsFiles) { |
| 145 | + final zipFile = new File(symbolsPath + '.zip') |
| 146 | + final zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile)) |
| 147 | + symbolsFiles.forEach { file -> |
| 148 | + final fileName = file.path.substring(symbolsPath.length() + 1) |
| 149 | + final zipEntry = new ZipEntry(fileName) |
| 150 | + zipOutputStream.putNextEntry(zipEntry) |
| 151 | + FileInputStream fileInputStream = new FileInputStream(file) |
| 152 | + final buffer = new byte[1024] |
| 153 | + int length = fileInputStream.read(buffer) |
| 154 | + while (length > 0) { |
| 155 | + zipOutputStream.write(buffer, 0, length) |
| 156 | + length = fileInputStream.read(buffer) |
| 157 | + } |
| 158 | + fileInputStream.close() |
| 159 | + zipOutputStream.closeEntry() |
| 160 | + } |
| 161 | + zipOutputStream.close() |
| 162 | + println "Instabug: Zip file created at ${zipFile.path}" |
| 163 | + return zipFile |
| 164 | +} |
| 165 | +/** |
| 166 | + * upload symbol files to Instabug endpoint |
| 167 | + * |
| 168 | + * @param zipFile the zip file containing the symbols files |
| 169 | + * @param applicationToken the app token |
| 170 | + * @param apiKey the api key |
| 171 | + * @return void |
| 172 | + */ |
| 173 | +void upload_symbols(zipFile, applicationToken, apiKey, uploadUrl) { |
| 174 | + // Initialize connection |
| 175 | + final connection = new URL(uploadUrl).openConnection() as HttpURLConnection |
| 176 | + |
| 177 | + // Set connection headers and request method |
| 178 | + connection.setRequestMethod('POST') |
| 179 | + connection.setRequestProperty('Connection', 'Keep-Alive') |
| 180 | + connection.setRequestProperty('Content-Type', "multipart/form-data;boundary=${boundary}") |
| 181 | + connection.setRequestProperty('Accept-Encoding', 'gzip') |
| 182 | + connection.setRequestProperty('Content-Length', "${zipFile.length()}") |
| 183 | + connection.setDoOutput(true) |
| 184 | + |
| 185 | + // Initialize output stream and writer |
| 186 | + final outputStream = connection.getOutputStream() |
| 187 | + final charset = 'ISO-8859-1' |
| 188 | + final writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true) |
| 189 | + |
| 190 | + // Write application_token in the request body |
| 191 | + writeField(writer, 'application_token', applicationToken) |
| 192 | + |
| 193 | + // Write api_key in the request body |
| 194 | + writeField(writer, 'api_key', apiKey) |
| 195 | + |
| 196 | + // Write zip file in the request body |
| 197 | + final fileName = zipFile.getName() |
| 198 | + writeFile(writer, fileName, zipFile, outputStream) |
| 199 | + |
| 200 | + // Send the request |
| 201 | + writer.close() |
| 202 | + |
| 203 | + // Get the response |
| 204 | + final responseCode = connection.getResponseCode() |
| 205 | + println "Instabug: Response Code: ${responseCode}" |
| 206 | + println "Instabug: Response Message: ${connection.getResponseMessage()}" |
| 207 | + if (responseCode == 200) { |
| 208 | + println 'Instabug: Upload successful' |
| 209 | + } else { |
| 210 | + println 'Instabug: Upload failed' |
| 211 | + } |
| 212 | + |
| 213 | + // Close the connection |
| 214 | + connection.disconnect() |
| 215 | + // Delete the zip file |
| 216 | + zipFile.delete() |
| 217 | +} |
| 218 | +/** |
| 219 | + * write a field in the request body |
| 220 | + * @param writer the writer |
| 221 | + * @param field the name of the field |
| 222 | + * @param value the value of the field |
| 223 | + * @return void |
| 224 | + */ |
| 225 | +void writeField(writer, field, value) { |
| 226 | + writer.append('--' + boundary).append(lineEnd) |
| 227 | + writer.append("Content-Disposition: form-data; name=\"" + field + "\"").append(lineEnd) |
| 228 | + writer.append(lineEnd) |
| 229 | + writer.append(value).append(lineEnd) |
| 230 | +} |
| 231 | +/** |
| 232 | + * write a field in the request body |
| 233 | + * @param writer the writer |
| 234 | + * @param fileName the name of the file |
| 235 | + * @param file the file to be uploaded |
| 236 | + * @param outputStream the output stream |
| 237 | + * @return void |
| 238 | + */ |
| 239 | +void writeFile(writer, fileName, file, outputStream) { |
| 240 | + writer.append('--' + boundary).append(lineEnd) |
| 241 | + writer.append("Content-Disposition: form-data; name=\"" + 'file' + "\"; filename=\"" + fileName + "\"").append(lineEnd); |
| 242 | + writer.append('Content-Type: ' + URLConnection.guessContentTypeFromName(fileName)).append(lineEnd) |
| 243 | + writer.append(lineEnd) |
| 244 | + writer.flush() |
| 245 | + |
| 246 | + final inputStream = new FileInputStream(file) |
| 247 | + byte[] buffer = new byte[4096] |
| 248 | + int bytesRead = -1 |
| 249 | + while ((bytesRead = inputStream.read(buffer)) != -1) { |
| 250 | + outputStream.write(buffer, 0, bytesRead) |
| 251 | + } |
| 252 | + outputStream.flush() |
| 253 | + inputStream.close() |
| 254 | + |
| 255 | + writer.append('--' + boundary).append(lineEnd) |
| 256 | + writer.flush() |
| 257 | +} |
0 commit comments