By the end of this module, we will:

  • List the some advantages of a robust workflow automation solution like Snakemake.
  • Describe the fundamental relationship between Snakemake, a Snakefile, and the file system.
  • Describe key parts of a Snakemake rule.
  • Build a Snakefile and use Snakemake to automate a simple workflow.
  • Use wildcards to easily extend a workflow to new inputs.
  • Show a Snakemake dry-run and other ways to visualize the workflow.

What is Snakemake?

Snakemake is a python program that interprets a text file describing your workflow against the file system and uses rules in that workflow to transform inputs into outputs as necessary. Snakemake is essentially declarative meaning that you don’t tell it what to do, you simply ask for the final output and Snakemake figures out what it needs to do. That declarative pattern supports several key attributes:

  • Snakemake workflows can gracefully extend to new inputs.
  • Snakemake is modular.
  • Snakemake is portable.
  • Snakemake is “durable” (i.e it crashes with poise).
  • Snakemake is efficient.

Build a Snakefile

A Snakefile is a text file that decribes your workflow. It is interpreted by the snakemake command. Building a good Snakefile is the key step in understanding Snakemake, so we’ll begin there.

Consider a sample bash script

To build a Snakefile it’s helpful to use a concrete example and also helpful to adapt an existing script. The script below considers the 1868 novel “Little Women”, by Louisa May Alcott. It outputs a file containing the ranked name counts for each of the four March sisters: Amy, Beth, Jo, and Laurie. (It also emits a few intermediate files).

alcott_script/alcott_script.sh
#!/bin/bash
# Which of the March sisters is referred to most often in
# part 1 of the Little Women?

# Split lines into words
cat inputs/little_women_part_1.txt \
    | tr -cs '[:alpha:]' '\n' \
    > 1.split_words.txt

# Count words
sort 1.split_words.txt | uniq -c \
    > 2.count_words.txt

# Sort words by descending count and add header
sort -k1,1nr 2.count_words.txt \
    | awk 'BEGIN {print "word\tcount"} { print $2 "\t" $1}' \
    > 3.sort_counts.txt

# Select names with respective counts
egrep '^(Jo|Amy|Laurie|Beth)\s' 3.sort_counts.txt > 4.select_words.txt


It’s not necessary to understand the command in detail, but it is helpful to consider the steps with their various outputs:

Inputs, steps, and outputs
inputs/little_women_part_1.txt
CHAPTER ONE

PLAYING PILGRIMS

"Christmas won't be Christmas without any presents," grumbled Jo, lying
on the rug.

"It's so dreadful to be poor!" sighed Meg, looking down at her old
dress.
...
1.split_words.txt
CHAPTER
ONE
PLAYING
PILGRIMS
Christmas
won
t
be
Christmas
without
2.count_words.txt
   1992 a
     75 A
      1 aback
      1 abase
      3 abashed
      1 abed
      1 abject
      3 able
      3 abominable
      1 abominably
3.sort_counts.txt
word    count
and 3811
the 3303
to  2303
a   1992
I   1988
her 1660
of  1490
in  1094
you 1010
4.select_words.txt
Jo  737
Laurie  319
Beth    294
Amy 283


To view the results interactively, you can execute the script.

export WORKSHOP_HOME="/nfs/turbo/umms-bioinf-wkshp/workshop/home/${USER}"
cd $WORKSHOP_HOME/project_alcott/alcott_script

./alcott_script.sh

And view excepts of the results in less. (Hit q key to exit less.)

head inputs/little_women_part_1.txt *.txt | less

Snakemake and the Snakefile

Switch to the alcott_snakemake dir; note there are inputs and a Snakefile

cd $WORKSHOP_HOME/project_alcott/alcott_snakemake
tree
.
├── inputs
│   ├── little_women_part_1.txt
│   └── little_women_part_2.txt
└── workflow
└── Snakefile

A Snakefile is a simple markdown file.By convention, the snakefile is named Snakefile and lives in the workflow sub-directory. The current Snakefile is simply a stub based on the earlier bash script:

workflow/Snakefile
# split_lines ###########################################################
#cat inputs/little_women_part_1.txt \
#    | tr -cs '[:alpha:]' '\n' \
#    > 1.split_words.txt
# count_words ###########################################################
#sort 1.split_words.txt | uniq -c \
#    > 2.count_words.txt
## sort_count ###########################################################
#sort -k1,1nr 2.count_words.txt \
#    | awk 'BEGIN {print "word\tcount"} { print $2 "\t" $1}' \
#    > 3.sort_counts.txt
# select_words ##########################################################
#egrep '^(Jo|Amy|Laurie|Beth)\s' 3.sort_counts.txt > 4.select_words.txt

A snakefile is composed of rules; each rule has a name and specifies directives such as

  • input (what the rule requires)
  • output (what the rule promises to generate)
  • shell (how it will make input into output)

We add the first step in the workflow as a new rule. (Adding below the existing comment block.) Note that we will keep the input files and result files in separate directories.

rule split_words:
    input: "inputs/little_women_part_1.txt"
    output: "results/little_women_part_1.split_words.txt" 
    shell: "cat inputs/little_women_part_1.txt | tr -cs '[:alpha:]' '\n' > results/little_women_part_1.split_words.txt"

Running Snakemake

  • The snakemake python program interprets the Snakefile and executes rules as appropriate to produce the expected output.
  • The snakemake program is installed as a module on Great Lakes.
  • When launching snakemake, You specify the number of cores (i.e. CPUs) with the -c flag. We will use 1 core.
module load snakemake
module list
# confirm you are in the right working directory
tree
Currently Loaded Modules:
1) python3.10-anaconda/2023.03   2) snakemake/7.32.4
$ tree
.
├── inputs
│   ├── little_women_part_1.txt
│   └── little_women_part_2.txt
└── workflow
└── Snakefile

Run snakemake command as below. Note:

  • snakemake will look for a Snakefile in the workflow directory.
  • snakemake emits a log to the screen that include
    • run details
    • Job stats
    • Rule details
    • Completion status i.e. (100%) done
snakemake -c1
Building DAG of jobs...
Using shell: /usr/bin/bash
Provided cores: 1 (use --cores to define parallelism)
Rules claiming more threads will be scaled down.
Job stats:
job            count
-----------  -------
split_words        1
total              1
.
Select jobs to execute...
.
[Mon Jun 10 18:35:41 2024]
rule split_words:
input: inputs/little_women_part_1.txt
output: results/little_women_part_1.split_words.txt
jobid: 0
reason: Missing output files: results/little_women_part_1.split_words.txt
resources: tmpdir=/tmp
.
[Mon Jun 10 18:35:41 2024]
Finished job 0.
1 of 1 steps (100%) done
Complete log: .snakemake/log/2024-06-10T183541.204636.snakemake.log

Check the file system. Note new results/ dir and new file. Note that snakemake created the results/ dir automatically.

tree
.
├── inputs
│   ├── little_women_part_1.txt
│   └── little_women_part_2.txt
├── results
│   └── little_women_part_1.split_words.txt
└── workflow
└── Snakefile

Also note that snakemake creates a hidden directory .snakemake/ in the working directory. This holds various info that help snakemake manage the workflow. It’s extremely rare for you to need to examine the contents of the .snakemake/ directory, but FYI.

If you re-run snakemake it determines the filesystem is up to date and no rules need to be triggered.

snakemake -c1
Building DAG of jobs...
Nothing to be done (all requested files are present and up to date).
Complete log: .snakemake/log/2024-06-10T184850.944535.snakemake.log

Add a new rule

In the Snakemake file, add the second step as a new rule below the first rule. Note that the inputs of this rule match the outputs of the prior rule.

rule count_words:
    input: "results/little_women_part_1.split_words.txt"
    output: "results/little_women_part_1.count_words.txt"
    shell: "sort results/little_women_part_1.split_words.txt | uniq -c > results/little_women_part_1.count_words.txt"

By default, snakemake only runs the first rule in the Snakefile. To trigger the new rule, you can specify the desired end target on the command line.

snakemake -c1 count_words

Note the log output similar to above, but now shows the new rule.

Building DAG of jobs...
Using shell: /usr/bin/bash
Provided cores: 1 (use --cores to define parallelism)
Rules claiming more threads will be scaled down.
Job stats:
job            count
-----------  -------
count_words        1
total              1
.
Select jobs to execute...
.
[Mon Jun 10 18:52:07 2024]
rule count_words:
input: results/little_women_part_1.split_words.txt
output: results/little_women_part_1.count_words.txt
jobid: 0
reason: Missing output files: results/little_women_part_1.count_words.txt
resources: tmpdir=/tmp
.
[Mon Jun 10 18:52:08 2024]
Finished job 0.
1 of 1 steps (100%) done
Complete log: .snakemake/log/2024-06-10T185207.598695.snakemake.log

See the new file and review the contents:

head results/*
==> results/little_women_part1.count_words.txt <==
1992 a
75 A
1 aback
1 abase
3 abashed
1 abed
1 abject
3 able
3 abominable
1 abominably
.
==> results/little_women_part_1.split_words.txt <==
CHAPTER
ONE
PLAYING
PILGRIMS
Christmas
won
t
be
Christmas
without

Variables / Rule ordering

Each rule defines input and output variables; you can use those values in the shell directive:

rule split_words:
    input: "inputs/little_women_part_1.txt"
    output: "results/little_women_part_1.split_words.txt"
    shell: "cat {input} | tr -cs '[:alpha:]' '\n' > {output}"

rule count_words:
    input: "results/little_women_part_1.split_words.txt"
    output: "results/little_women_part1.count_words.txt"
    shell: "sort {input} | uniq -c > {output}"

Snakemake doesn’t care what order the rules appear in the Snakefile but it assumes the first rule is the default target. Put the last step of the workflow at the top of the file and snakemake will default to running the whole workflow.

rule count_words:
    input: "results/little_women_part_1.split_words.txt"
    output: "results/little_women_part1.count_words.txt"
    shell: "sort {input} | uniq -c > {output}"

rule split_words:
    input: "inputs/little_women_part_1.txt"
    output: "results/little_women_part_1.split_words.txt"
    shell: "cat {input} | tr -cs '[:alpha:]' '\n' > {output}"

Rerun after making these changes, and check out the log (excepted below).

  • Note that in Job stats all rules are triggered.
  • Note the value of reason in each of the rules. Does the reason make sense?
snakemake -c1
...
Job stats:
job            count
-----------  -------
count_words        1
split_words        1
total              2
...
rule split_words:
input: inputs/little_women_part_1.txt
output: results/little_women_part_1.split_words.txt
jobid: 1
reason: Code has changed since last execution
resources: tmpdir=/tmp
...
rule count_words:
input: results/little_women_part_1.split_words.txt
output: results/little_women_part1.count_words.txt
jobid: 0
reason: Input files updated by another job: results/little_women_part_1.split_words.txt
resources: tmpdir=/tmp

Using triple quotes / Escaping curly braces

Add the third step of the transform

  • Place it above the existing rules.
  • Use {input} and {output} variables.
  • We use triple quotes to make multiline shell commands.
  • In the shell, any braces ({}) related to the command must be doubled to distinguish them from Snakefile variables.
rule sort_counts:
    input: "results/little_women_part_1.count_words.txt"
    output: "results/little_women_part_1.sort_counts.txt"
    shell: """
    sort -k1,1nr {input} \
        | awk 'BEGIN {{print "word\tcount"}} {{ print $2 "\t" $1}}' \
            > {output}
        """

Note that when you run snakemake it only generates the files it needs to.

snakemake -c1
...
Job stats:
job            count
-----------  -------
sort_counts        1
total              1
...
rule sort_counts:
input: results/little_women_part_1.count_words.txt
output: results/little_women_part_1.sort_counts.txt
jobid: 0
reason: Missing output files: results/little_women_part_1.sort_counts.txt
resources: tmpdir=/tmp
...
1 of 1 steps (100%) done

Use triple quotes to create multiline shell commands. If your commands use curly braces ({}) you need to double them so snakemake doesn’t get confused.

Snakemake dry-run / Interpreting rule errors

Add the fourth rule, placing it above the prior.

rule select_words:
    input: "results/little_women_part_1.sort_counts.txt"
    output: "results/little_women_part_1.select_words.txt"
    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

You probably have noticed that snakemake will only run the rules it needs to, based on changes in files or scripts or missing files. You can preview what snakemake will do by using the dry-run flag --dry-run or -n. This will show the typical snakemake output but will not change any files.

snakemake -c1 --dry-run
Building DAG of jobs...
Job stats:
job             count
------------  -------
select_words        1
total               1
.
[Mon Jun 10 19:41:43 2024]
rule select_words:
input: results/little_women_part_1.sort_counts.txt
output: results/little_women_part_1.select_words.txt
jobid: 0
reason: Missing output files: results/little_women_part_1.select_words.txt
resources: tmpdir=/tmp
.
Job stats:
job             count
------------  -------
select_words        1
total               1
.
Reasons:
(check individual jobs above for details)
missing output files:
select_words
.
This was a dry-run (flag -n). The order of jobs does not reflect the order of execution.

Developing a Snakefile typically involves some troubleshooting along the way. I’ve intentionally introduced a typo in the Snakefile and then did a dry-run to show an Snakefile error message:

$ snakemake -c1 --dry-run
.
A  Building DAG of jobs...
B  MissingInputException in rule select_words in file .../Snakefile, line 15:
C  Missing input files for rule select_words:
D    output: results/little_women_part_1.select_words.txt
E    affected files:
F        result/little_women_part_1.sort_counts.txt

Note:

  • B line number (line 15)
  • C Missing input files for rule select_words
  • E & F result/little_women_part_1.sort_counts.txt

Can you see the error? You will likely need to consider the rule in context:

rule select_words:
    input: "result/little_women_part_1.sort_counts.txt"
    output: "results/little_women_part_1.select_words.txt"
    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

rule sort_counts:
    input: "results/little_women_part_1.count_words.txt"
    output: "results/little_women_part_1.sort_counts.txt"
    shell: """
    sort -k1,1nr {input} \
        | awk 'BEGIN {{print "word\tcount"}} {{ print $2 "\t" $1}}' \
            > {output}
        """
rule count_words:
    input: "results/little_women_part_1.split_words.txt"
    output: "results/little_women_part_1.count_words.txt"
    shell: "sort {input} | uniq -c > {output}"

rule split_words:
    input: "inputs/little_women_part_1.txt"
    output: "results/little_women_part_1.split_words.txt"
    shell: "cat {input} | tr -cs '[:alpha:]' '\n' > {output}"

Fix the typo and confirm the fix using --dry-run to get a clean output. Then execute snakemake normally until you are 100% done.

Visualizing the DAG / rulegraph

In addition to doing a dry run, snakemake can generate a diagram of the rules to execute. Snakemake thinks of the rules as a Directed Acyclic Graph (DAG). You can see this with the --dag flag and the dot program (part of Graphviz, an open source graph visualizer).

snakemake --dag | dot -Tpng > dag.png

dag.png

As you can see above, by default dot displays the graphs vertically with inputs at the top and outputs at the bottom. Our Snakefile is arranged from output to input, so you can invert the diagram like so:

snakemake --dag | dot -Grankdir="BT" -Tpng > dag_inverted.png

dag_inverted.png

All target / Wildcards / Visualizaing the rulegraph

To emphasize the endpoint of the workflow and also clarify the default target, it’s common to place a “rule all” at the top of the file. (This also allows you to reorder rules within the file arbitrarily.) It is unusual in that it only has an input directive.

rule all:
    input: "results/little_women_part_1.select_words.txt"

Snakemake wildcards make it easy to to extend the workflow to new inputs. (And they typically reduce repetition of file names.) Here is an excerpt of the Snakefile showing the original select_words rule and the same rule with wildcards.

rule all:
    input: "results/little_women_part_1.select_words.txt"

# original rule
# rule select_words:
#    input: "result/little_women_part_1.sort_counts.txt"
#    output: "results/little_women_part_1.select_words.txt"
#    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

# now with wildcards
rule select_words:
    input: "result/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

How does this work?

  • The all rule needs an input named “results/little_women_part_1.select_words.txt”
  • The select_words rule can produce “results/{base_name}.select_words.txt” which would match the all input if the wildcard {base_name}=“little_women_part_1”
  • In select_word input, the rule substitutes the {base_name} value for it’s wildcard, making “result/little_women_part_1.sort_counts.txt”
  • So the the value of the wildcard is determined by the file name in the all rule and that value propagates from output to input.

What happens if we run snakemake?

snakemake -c1 --dry-run
Building DAG of jobs...
Nothing to be done (all requested files are present and up to date).

And that actually makes sense: even though we used wildcards, the rules ended up with identical input and output values.

We can extend wildcards across remaining rules as below. Note that we need to keep the literal value in the all rule so the other rules can derive the value of the wildcard.

rule all:
    input: "results/little_women_part_1.select_words.txt"

rule select_words:
    input: "results/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

rule sort_counts:
    input: "results/{base_name}.count_words.txt"
    output: "results/{base_name}.sort_counts.txt"
    shell: """
    sort -k1,1nr {input} \
        | awk 'BEGIN {{print "word\tcount"}} {{ print $2 "\t" $1}}' \
            > {output}
        """
rule count_words:
    input: "results/{base_name}.split_words.txt"
    output: "results/{base_name}.count_words.txt"
    shell: "sort {input} | uniq -c > {output}"

rule split_words:
    input: "inputs/{base_name}.txt"
    output: "results/{base_name}.split_words.txt"
    shell: "cat {input} | tr -cs '[:alpha:]' '\n' > {output}"

Now that the Snakefile is using wildcards, it is trivial to extend it to new inputs. We adjust the all rule adding a second value, separated by a comma:

rule all:
    input: "results/little_women_part_1.select_words.txt", "results/little_women_part_2.select_words.txt"
...

The addition of a new wildcard value (little_women_part_2) will trigger all five rules.

snakemake --dry-run
...
Job stats:
job             count
------------  -------
all                 1
count_words         1
select_words        1
sort_counts         1
split_words         1
total               5
...

Before we run this for real, let’s add a new flag --forceall to tell snakemake to trigger every single rule for all possible inputs. Basically act like there are no existing outputs and it needs to start from scratch. But this is still a dry-run, so no files will be changed.

snakemake --dry-run --forceall
...
Job stats:
job             count
------------  -------
all                 1
count_words         2
select_words        2
sort_counts         2
split_words         2
total               9
...

Try generating a DAG visualization. (Now it’s truly looking more like a graph.) Note:

  • Each wildcard value gets it’s own path
  • The wildcard values are displayed at the input
  • Some box outlines are solid and other are dotted; why is that?
snakemake --dag | dot -Grankdir="BT" -Tpng > dag_inverted_wildcards.png

dag_inverted_wildcards.png

With complex workflows, you sometimes don’t want to see all the possible inputs and would rather just see the abstract relationship of the rules. Snakemake supports this with a related flag --rulegraph

snakemake --rulegraph | dot -Grankdir="BT" -Tpng > rulegraph.png

rulegraph.png

Ok. Go ahead and run snakemake until you are 100% complete and see these files in results:

$ ls results
little_women_part_1.count_words.txt   little_women_part_2.count_words.txt
little_women_part_1.select_words.txt  little_women_part_2.select_words.txt
little_women_part_1.sort_counts.txt   little_women_part_2.sort_counts.txt
little_women_part_1.split_words.txt   little_women_part_2.split_words.txt

The expand function / config files / parameters

Ideally, a workflow could be run on a new set of inputs without changing the Snakefile at all. For example, we should be able to use our unmodified Snakefile with the input of of Shakespeare’s Romeo and Juliet to see whether Montague is mentioned more than Capulet. Most of the rules in the workflow are already general enough; but there are two exceptions:

  • The all rule mentions specific texts.
  • The select_words rule mentions specific names.

We can extract these specifics from the Snakefile using a configuration file. We’ll start with the select_words rule.

rule select_words:
    input: "results/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    shell: "egrep '^(Jo|Amy|Laurie|Beth)\s' {input} > {output}"

Firstly, we’ll pull the regular expression out of the shell directive into a configuration file. Snakemake supports several styles of configuration file; we’ll use the YAML syntax. By convention we’ll create config/config.yaml; you could create and edit this file manually; alternatively you can also execute the line below:

mkdir config
echo 'select_words_regex: ^(Jo|Amy|Laurie|Beth)\s' > config/config.yaml
cat config/config.yaml

Now add this line above the all rule:

configfile: "config/config.yaml"

Add a parameter initialized to the config key select_words_regex. Then adjust the select_words rule to refernce that parameter.

rule select_words:
    input: "results/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    params: regex=config[select_words_regex]
    shell: "egrep '{params.regex}' {input} > {output}"

The params directive is a way of adjusting the shell command with a value that isn’t tied to the filesystem. Also note that params can be named (e.g. x="value") and referenced by name in the shell ({params.x}).

Now that we’ve made this change, let’s dry-run snakemake. Note we’re adding the -p flag so snakemake will print out the actual commands it will execute:

snakemake --dry-run -p

In the output, note:

  • In Job stats, we see snakemake believes we need to rerun select_words for both inputs.
  • In the rule details, the reason the rule is triggered is that code and params have changed
  • Because we used -p, you can see the actual egrep statement (which looks fine).
Building DAG of jobs...
Job stats:
job             count
------------  -------
all                 1
select_words        2
total               3
.
[Mon Jun 10 23:01:21 2024]
rule select_words:
input: results/little_women_part_1.sort_counts.txt
output: results/little_women_part_1.select_words.txt
jobid: 1
reason: Code has changed since last execution; Params have changed since last execution
wildcards: base_name=little_women_part_1
resources: tmpdir=/tmp
.
egrep '^(Jo|Amy|Laurie|Beth)\s' results/little_women_part_1.sort_counts.txt > results/little_women_part_1.select_words.txt
.
[Mon Jun 10 23:01:21 2024]
rule select_words:
input: results/little_women_part_2.sort_counts.txt
output: results/little_women_part_2.select_words.txt
jobid: 5
reason: Code has changed since last execution; Params have changed since last execution
wildcards: base_name=little_women_part_2
resources: tmpdir=/tmp
.
egrep '^(Jo|Amy|Laurie|Beth)\s' results/little_women_part_2.sort_counts.txt > results/little_women_part_2.select_words.txt
...

Go ahead and run snakemake until 100% done.

Now we’ll pull the file base names from the all rule into the config file:

select_words_regex: ^(Jo|Amy|Laurie|Beth)\s

input_base_filenames:
  - little_women_part_1
  - little_women_part_2

To get these values from config.yaml into the all rule, I need to introduce the expand function. expand is a helper function that comes with Snakemake. It’s easier to explain if we show a few examples of inputs and outputs:

expand("{dataset}/a.txt", dataset=[1, 2])
['1/a.txt', '2/a.txt', '3/a.txt']
.
expand("{a}.{b}", a=['fig1', 'fig2', 'fig3'], b=['png', 'pdf'])
['fig1.png', 'fig1.pdf', 'fig2.png', 'fig2.pdf', 'fig3.png', 'fig3.pdf']
.
expand("results/{x}.select_words.txt", \
...         x=["little_women_part_1", "little_women_part_2"])
['results/little_women_part_1.select_words.txt', 'results/little_women_part_2.select_words.txt']

expand accepts a string pattern with named placeholders (e.g. {dataset}) and named lists (e.g. dataset=[1, 2]); it builds a list of strings by inserting the list values into the pattern.

That last example above is exactly what we need in the all rule. The final step is to adapt the example above to use the config file, like so:

rule all:
    input: expand("results/{x}.select_words.txt", \
        x=config['input_base_filenames'])

The Snakefile is now free of any references to specific inputs. The workflow can be run on a different set of inputs by using a new config.yaml.

configfile: "config/config.yaml"

rule all:
    input: expand("results/{x}.select_words.txt", \
    x=config['input_base_filenames'])


rule select_words:
    input: "results/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    params: regex=config["select_words_regex"]
    shell: "egrep '{params.regex}' {input} > {output}"

rule sort_counts:
    input: "results/{base_name}.count_words.txt"
    output: "results/{base_name}.sort_counts.txt"
    shell: """
    sort -k1,1nr {input} \
        | awk 'BEGIN {{print "word\tcount"}} {{ print $2 "\t" $1}}' \
            > {output}
        """
rule count_words:
    input: "results/{base_name}.split_words.txt"
    output: "results/{base_name}.count_words.txt"
    shell: "sort {input} | uniq -c > {output}"

rule split_words:
    input: "inputs/{base_name}.txt"
    output: "results/{base_name}.split_words.txt"
    shell: "cat {input} | tr -cs '[:alpha:]' '\n' > {output}"

Snakemake on Great Lakes

All this time we’ve been executing the workflow on the login-nodes. It’s generally fine to execute the snakemake process itself on the login-node, but for a more demanding workflow you should execute the rules on Great Lakes worker nodes.

It’s straightforward to do this by using a profile. A profile is essentially just a little config file that tells snakemake how it should submit rules as jobs. Check out snakemake docs for details on profile options. We’ll start with a simple profile built for Great Lakes:

cp -r /nfs/turbo/umms-bioinf-wkshp/workshop/shared-data/profile/ config/
config/profile/config.yaml
printshellcmds: True
keep-going: True
use-singularity: True
singularity-args: "--cleanenv -B $(pwd)"
cluster: sbatch --job-name=alcott_{rule}_{wildcards} --account=bioinf_wkshp_class --partition=standard --nodes=1 --cpus-per-task={resources.cpus} --mem={resources.mem_mb} --time={resources.time_min} --output=cluster_logs/%x-%j.log --parsable
default-resources: [cpus=1, mem_mb=2000, time_min=120]
rerun-triggers: [mtime]
latency-wait: 300
jobs: 5


Invoking snakemake with this profile submits each rule a an sbatch job:

# clear out existing files so there's something to do
rm results/*

snakemake --profile config/profile

Note that you can adjust the SLURM job geometry by adding a resources directive to a rule. These values will be incorporated into the sbatch request for this rule.

rule select_words:
    input: "results/{base_name}.sort_counts.txt"
    output: "results/{base_name}.select_words.txt"
    resources: cpus=4, mem_mb=4000, time_min=5  
    params: regex=config["select_words_regex"]
    shell: "egrep '{params.regex}' {input} > {output} #--fake-num-threads={resources.cpus}"


Using tmux

tmux is a unix utility that let’s you create virtual screens inside of a single session. (It’s like the Unix utility screen, but more powerful and current.) For our purposes, the killer feature of tmux is captured in this simple vignette:

  • login to great lakes and start a tmux session on the login-node: tmux
  • start a long running task (e.g. a snakemake workflow which submits to the cluster)
  • detach from that tmux session: hit ctrl-b d
  • turn off your computer, go home, and login to great lakes from different computer
  • reattach to that tmux session: tmux attach
  • you will see your long-running task has been running all along, completely unaffected by your wanderings

And in fact, tmux can actually do much, much more. Here’s a nifty cheatsheet.

If you start using tmux, keep in mind that when you login into greatlakes.arc-ts.umich.edu it’s actually forwarding you on to one of three login nodes: gl-login1, gl-login2, or gl-login3. (Which one you get is basically random.) This means if you want to reconnect to a detached tmux session, you have to get to the right login node.

So, if you start a tmux session, note which login node you are using using the hostname command:

$ hostname
gl-login2.arc-ts.umich.edu

Then when you need to later reconnect to that session, instead of ssh greatlakes.arc-ts.umich.edu you connect to the specific hostname you got above:

ssh gl-login2.arc-ts.umich.edu
# login + Duo
# login banner (blah blah blah)
[cgates@gl-login2 ~]$ tmux ls # list active tmux sessions
0: 1 windows (created Tue Jun 11 20:31:03 2024) [133x25]
[cgates@gl-login2 ~]$ tmux attach # attach to my session

Voila. You are back in your tmux session, right where you left it.

Lastly, the scrollbar in the terminal/command window does not work in tmux. If you need to scroll in tmux, you can hit ctrl-`b and then [ to enter scroll mode. You can then use cursor or page-up, page-down to move around the tmux scroll-buffer. Hit q to quit scoll mode and return to the current prompt.


Pro tips

  1. Start learning Snakemake with a simple workflow. Don’t start with a really complex workflow.
  2. It’s ok to build your Snakefile from the first steps toward the last, but remember that Snakemake “thinks” from the last output backward.
  3. Keep your inputs, outputs, and workflow in separate directories.
  4. Build your Snakefile incrementally.
    1. Start with a single input.
    2. Add a rule at a time.
    3. Make small changes and use dry-run liberally.
    4. OK to start by hardcoding filenames; put the wildcards in afterward.
  5. Keep consistent naming conventions. Including your rule names in your file names will help you troubleshoot. Some people include numbers in the file names or place different outputs in different directories. Choose a pattern that works for you and be consistent.
  6. If you subset your input data so that your steps are fast and light, you can prototype the Snakefile on the login-nodes. (But be ready to hit control-C if you wander into a computationally intensive step.)
  7. Use spaces not tabs.
  8. Use config files to avoid hardcoding paths or “magic values” in the Snakefile.
  9. Checkout Snakemake’s styleguide on Distribution and Reproducibility. It has great recommendations on how to organize files and directories.

Key ideas

  • A robust workflow automation solution is great because:
    • it supports simple (e.g. linear) and complex (e.g. graph) transformations equally well
    • only outstanding steps are run and individual steps are atomic
    • it’s designed with software dependency management in mind
    • it’s easy to tune, resulting in higher resource efficiency
    • it’s portable across execution environments
    • it’s designed to gracefully scale to many inputs
    • all the capabilities above don’t obscure the actual data transfromation
  • Snakemake is a Python program that evaluates the rules in a Snakefile against the file system and applies rules as necessary to produce the desired output files.
  • The Snakefile is a simple markdown file composed of rules. Each rule contains directives that specify inputs, outputs, commands to run foe that rule.
  • Snakemake builds the intended set of rules into a directed acyclic graph (DAG).
  • The dry-run enables you to see the rules it will execute in advance.
  • You can visualize the Snakemake DAG or rulegraph using dot.
  • Command line flags we reviewed:
    • -c
    • -n –dry-run
    • –dag –rulegraph
    • –forceall
    • -p –printshellcmds

Exercises

Exercise 1A:

cd to the project publication_snakemake_1

Given this DAG can you fill in the missing parts of the Snakefile below and regenerate a similar DAG figure?

FYI:

  • All the rules you need are in place, but most of them have incomplete input and output directives.
  • The shell directives in this Snakefile reference “external” scripts in the workflow scripts directory. You will not need to change any shell directives for this exercise.
  • The actual results files for this workflow are merely stubs and not interesting. Generating the right Snakefile / DAG is the focus.
rule all:
    input: 'results/knit_paper.html'

rule knit_paper:
    input: 'inputs/intro.md', ??
    output: ??
    shell: 'workflow/scripts/knit_paper.sh {input} {output}'

rule make_plot:
    input: ??
    output: ??
    shell: 'workflow/scripts/make_plot.sh {input} {output}'

rule make_table:
    input: ??
    output: ??
    shell: 'workflow/scripts/make_table.sh {input} {output}'

rule filter:
    input: ??
    output: ??
    params: chromosome='6'
    shell: 'workflow/scripts/filter.sh {input} {output} {params.chromosome}'

rule download_data:
    input: '/nfs/turbo/umms-bioinf-wkshp/workshop/shared-data/agc-endpoint/0042-WS/sample_A.genome.bam'
    output:
    shell: 'workflow/scripts/download_data.sh {input} {output}'

Exercise 1B:

Use params and a config file to extract hardcoded absolute paths and any other “magic values”.

Exercise 1C:

Extend the workflow to cover more inputs as illustrated in the DAG below:

Exercise 1D:

Copy the profile/config.yaml from project_alcott/alcott_snakemake and force re-run the Snakefile using worker nodes instead of the login-node.


LS0tCnRpdGxlOiAiSW50cm8gdG8gU25ha2VtYWtlIgphdXRob3I6ICJVTSBCaW9pbmZvcm1hdGljcyBDb3JlIgpvdXRwdXQ6CiAgICAgICAgaHRtbF9kb2N1bWVudDoKICAgICAgICAgICAgaW5jbHVkZXM6CiAgICAgICAgICAgICAgICBpbl9oZWFkZXI6IGhlYWRlci5odG1sCiAgICAgICAgICAgIHRoZW1lOiBwYXBlcgogICAgICAgICAgICB0b2M6IHRydWUKICAgICAgICAgICAgdG9jX2RlcHRoOiA0CiAgICAgICAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICAgICAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICAgICAgICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICAgICAgICAgIG1hcmtkb3duOiBHRk0KICAgICAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KYm9keXsgLyogTm9ybWFsICAqLwogICAgICBmb250LXNpemU6IDE0cHQ7CiAgfQpwcmUgewogIGZvbnQtc2l6ZTogMTJwdAp9Cgoud3JhcHBlciB7IG92ZXJmbG93LXg6IGF1dG87IH0KLndyYXBwZXIgdGFibGUgeyB3aGl0ZS1zcGFjZTogbm93cmFwOyB9Cgo8L3N0eWxlPgoKQnkgdGhlIGVuZCBvZiB0aGlzIG1vZHVsZSwgd2Ugd2lsbDoKCiogTGlzdCB0aGUgc29tZSBhZHZhbnRhZ2VzIG9mIGEgcm9idXN0IHdvcmtmbG93IGF1dG9tYXRpb24gc29sdXRpb24gbGlrZSBTbmFrZW1ha2UuCiogRGVzY3JpYmUgdGhlIGZ1bmRhbWVudGFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIFNuYWtlbWFrZSwgYSBTbmFrZWZpbGUsIGFuZCB0aGUgZmlsZSBzeXN0ZW0uCiogRGVzY3JpYmUga2V5IHBhcnRzIG9mIGEgU25ha2VtYWtlIHJ1bGUuCiogQnVpbGQgYSBTbmFrZWZpbGUgYW5kIHVzZSBTbmFrZW1ha2UgdG8gYXV0b21hdGUgYSBzaW1wbGUgd29ya2Zsb3cuCiogVXNlIHdpbGRjYXJkcyB0byBlYXNpbHkgZXh0ZW5kIGEgd29ya2Zsb3cgdG8gbmV3IGlucHV0cy4KKiBTaG93IGEgU25ha2VtYWtlIGRyeS1ydW4gYW5kIG90aGVyIHdheXMgdG8gdmlzdWFsaXplIHRoZSB3b3JrZmxvdy4KCgojIyBXaGF0IGlzIFNuYWtlbWFrZT8KClNuYWtlbWFrZSBpcyBhIHB5dGhvbiBwcm9ncmFtIHRoYXQgaW50ZXJwcmV0cyBhIHRleHQgZmlsZSBkZXNjcmliaW5nIHlvdXIKd29ya2Zsb3cgYWdhaW5zdCB0aGUgZmlsZSBzeXN0ZW0gYW5kIHVzZXMgcnVsZXMgaW4gdGhhdCB3b3JrZmxvdyB0byB0cmFuc2Zvcm0KaW5wdXRzIGludG8gb3V0cHV0cyBhcyBuZWNlc3NhcnkuIFNuYWtlbWFrZSBpcyBlc3NlbnRpYWxseSAqKmRlY2xhcmF0aXZlKioKbWVhbmluZyB0aGF0IHlvdSBkb24ndCB0ZWxsIGl0IHdoYXQgdG8gZG8sIHlvdSBzaW1wbHkgYXNrIGZvciB0aGUgZmluYWwgb3V0cHV0CmFuZCBTbmFrZW1ha2UgZmlndXJlcyBvdXQgd2hhdCBpdCBuZWVkcyB0byBkby4gVGhhdCBkZWNsYXJhdGl2ZSBwYXR0ZXJuIHN1cHBvcnRzCnNldmVyYWwga2V5IGF0dHJpYnV0ZXM6CgotIFNuYWtlbWFrZSB3b3JrZmxvd3MgY2FuIGdyYWNlZnVsbHkgZXh0ZW5kIHRvIG5ldyBpbnB1dHMuCi0gU25ha2VtYWtlIGlzIG1vZHVsYXIuCi0gU25ha2VtYWtlIGlzIHBvcnRhYmxlLgotIFNuYWtlbWFrZSBpcyDigJxkdXJhYmxl4oCdIChpLmUgaXQgY3Jhc2hlcyB3aXRoIHBvaXNlKS4KLSBTbmFrZW1ha2UgaXMgZWZmaWNpZW50LgoKCiMjIEJ1aWxkIGEgU25ha2VmaWxlCgpBIFNuYWtlZmlsZSBpcyBhIHRleHQgZmlsZSB0aGF0IGRlY3JpYmVzIHlvdXIgd29ya2Zsb3cuIEl0IGlzIGludGVycHJldGVkIGJ5IHRoZQpgc25ha2VtYWtlYCBjb21tYW5kLiBCdWlsZGluZyBhIGdvb2QgU25ha2VmaWxlIGlzIHRoZSBrZXkgc3RlcCBpbiB1bmRlcnN0YW5kaW5nClNuYWtlbWFrZSwgc28gd2UnbGwgYmVnaW4gdGhlcmUuCgojIyMgQ29uc2lkZXIgYSBzYW1wbGUgYmFzaCBzY3JpcHQKClRvIGJ1aWxkIGEgU25ha2VmaWxlIGl0J3MgaGVscGZ1bCB0byB1c2UgYSBjb25jcmV0ZSBleGFtcGxlIGFuZCBhbHNvIGhlbHBmdWwgdG8KYWRhcHQgYW4gZXhpc3Rpbmcgc2NyaXB0LiBUaGUgc2NyaXB0IGJlbG93IGNvbnNpZGVycyB0aGUgMTg2OCBub3ZlbCAiTGl0dGxlCldvbWVuIiwgYnkgTG91aXNhIE1heSBBbGNvdHQuIEl0IG91dHB1dHMgYSBmaWxlIGNvbnRhaW5pbmcgdGhlIHJhbmtlZCBuYW1lCmNvdW50cyBmb3IgZWFjaCBvZiB0aGUgZm91ciBNYXJjaCBzaXN0ZXJzOiBBbXksIEJldGgsIEpvLCBhbmQgTGF1cmllLiAoSXQKYWxzbyBlbWl0cyBhIGZldyBpbnRlcm1lZGlhdGUgZmlsZXMpLgoKCjx0YWJsZSBjbGFzcz0nZmlnJz48dHI+PHRoIGNsYXNzPSdmaWcnIGNvbHNwYW49IjIiPmFsY290dF9zY3JpcHQvYWxjb3R0X3NjcmlwdC5zaDwvdGg+PC90cj4KPHRyPgo8dGQgY2xhc3M9J2ZpZyc+PGltZyBzcmM9ImltYWdlcy9Nb2R1bGUwN19iYXNoX2RhZy5wbmciLz48L3RkPgo8dGQgY2xhc3M9J2ZpZyc+PHByZT4KIyEvYmluL2Jhc2gKIyBXaGljaCBvZiB0aGUgTWFyY2ggc2lzdGVycyBpcyByZWZlcnJlZCB0byBtb3N0IG9mdGVuIGluCiMgcGFydCAxIG9mIHRoZSBMaXR0bGUgV29tZW4/CgojIFNwbGl0IGxpbmVzIGludG8gd29yZHMKY2F0IGlucHV0cy9saXR0bGVfd29tZW5fcGFydF8xLnR4dCBcCiAgICB8IHRyIC1jcyAnWzphbHBoYTpdJyAnXG4nIFwKICAgID4gMS5zcGxpdF93b3Jkcy50eHQKCiMgQ291bnQgd29yZHMKc29ydCAxLnNwbGl0X3dvcmRzLnR4dCB8IHVuaXEgLWMgXAogICAgPiAyLmNvdW50X3dvcmRzLnR4dAoKIyBTb3J0IHdvcmRzIGJ5IGRlc2NlbmRpbmcgY291bnQgYW5kIGFkZCBoZWFkZXIKc29ydCAtazEsMW5yIDIuY291bnRfd29yZHMudHh0IFwKICAgIHwgYXdrICdCRUdJTiB7cHJpbnQgIndvcmRcdGNvdW50In0geyBwcmludCAkMiAiXHQiICQxfScgXAogICAgPiAzLnNvcnRfY291bnRzLnR4dAoKIyBTZWxlY3QgbmFtZXMgd2l0aCByZXNwZWN0aXZlIGNvdW50cwplZ3JlcCAnXihKb3xBbXl8TGF1cmllfEJldGgpXHMnIDMuc29ydF9jb3VudHMudHh0ID4gNC5zZWxlY3Rfd29yZHMudHh0CjwvcHJlPjwvdGQ+CjwvdHI+PC90YWJsZT4KPGJyLz4KCgpJdCdzIG5vdCBuZWNlc3NhcnkgdG8gdW5kZXJzdGFuZCB0aGUgY29tbWFuZCBpbiBkZXRhaWwsIGJ1dCBpdCBpcyBoZWxwZnVsIHRvIApjb25zaWRlciB0aGUgc3RlcHMgd2l0aCB0aGVpciB2YXJpb3VzIG91dHB1dHM6CgoKPHRhYmxlIGNsYXNzPSdmaWcnPjx0cj48dGggY2xhc3M9J2ZpZyc+SW5wdXRzLCBzdGVwcywgYW5kIG91dHB1dHM8L3RoPjwvdHI+Cjx0cj48dGQgY2xhc3M9J2ZpZyc+aW5wdXRzL2xpdHRsZV93b21lbl9wYXJ0XzEudHh0PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+CkNIQVBURVIgT05FCgpQTEFZSU5HIFBJTEdSSU1TCgoiQ2hyaXN0bWFzIHdvbid0IGJlIENocmlzdG1hcyB3aXRob3V0IGFueSBwcmVzZW50cywiIGdydW1ibGVkIEpvLCBseWluZwpvbiB0aGUgcnVnLgoKIkl0J3Mgc28gZHJlYWRmdWwgdG8gYmUgcG9vciEiIHNpZ2hlZCBNZWcsIGxvb2tpbmcgZG93biBhdCBoZXIgb2xkCmRyZXNzLgouLi4KPC9wcmU+PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjEuc3BsaXRfd29yZHMudHh0PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+Q0hBUFRFUgpPTkUKUExBWUlORwpQSUxHUklNUwpDaHJpc3RtYXMKd29uCnQKYmUKQ2hyaXN0bWFzCndpdGhvdXQKPC9wcmU+PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjIuY291bnRfd29yZHMudHh0PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+CiAgIDE5OTIgYQogICAgIDc1IEEKICAgICAgMSBhYmFjawogICAgICAxIGFiYXNlCiAgICAgIDMgYWJhc2hlZAogICAgICAxIGFiZWQKICAgICAgMSBhYmplY3QKICAgICAgMyBhYmxlCiAgICAgIDMgYWJvbWluYWJsZQogICAgICAxIGFib21pbmFibHkKPC9wcmU+PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjMuc29ydF9jb3VudHMudHh0PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+CndvcmQJY291bnQKYW5kCTM4MTEKdGhlCTMzMDMKdG8JMjMwMwphCTE5OTIKSQkxOTg4CmhlcgkxNjYwCm9mCTE0OTAKaW4JMTA5NAp5b3UJMTAxMAo8L3ByZT48L3RkPjwvdHI+Cjx0cj48dGQgY2xhc3M9J2ZpZyc+NC5zZWxlY3Rfd29yZHMudHh0PC90ZD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+CkpvCTczNwpMYXVyaWUJMzE5CkJldGgJMjk0CkFteQkyODMKPC9wcmU+PC90ZD48L3RyPgo8L3RhYmxlPgo8YnIvPgoKClRvIHZpZXcgdGhlIHJlc3VsdHMgaW50ZXJhY3RpdmVseSwgeW91IGNhbiBleGVjdXRlIHRoZSBzY3JpcHQuCgpgYGBzaApleHBvcnQgV09SS1NIT1BfSE9NRT0iL25mcy90dXJiby91bW1zLWJpb2luZi13a3NocC93b3Jrc2hvcC9ob21lLyR7VVNFUn0iCmNkICRXT1JLU0hPUF9IT01FL3Byb2plY3RfYWxjb3R0L2FsY290dF9zY3JpcHQKCi4vYWxjb3R0X3NjcmlwdC5zaApgYGAKCkFuZCB2aWV3IGV4Y2VwdHMgb2YgdGhlIHJlc3VsdHMgaW4gYGxlc3NgLiAoSGl0IGBxYCBrZXkgdG8gZXhpdCBgbGVzc2AuKQoKYGBgc2gKaGVhZCBpbnB1dHMvbGl0dGxlX3dvbWVuX3BhcnRfMS50eHQgKi50eHQgfCBsZXNzCmBgYAoKIyMjIFNuYWtlbWFrZSBhbmQgdGhlIFNuYWtlZmlsZQoKU3dpdGNoIHRvIHRoZSBhbGNvdHRfc25ha2VtYWtlIGRpcjsgbm90ZSB0aGVyZSBhcmUgaW5wdXRzIGFuZCBhIFNuYWtlZmlsZQpgYGBzaApjZCAkV09SS1NIT1BfSE9NRS9wcm9qZWN0X2FsY290dC9hbGNvdHRfc25ha2VtYWtlCnRyZWUKYGBgCj4gYGBgCi4K4pSc4pSA4pSAIGlucHV0cwrilILCoMKgIOKUnOKUgOKUgCBsaXR0bGVfd29tZW5fcGFydF8xLnR4dArilILCoMKgIOKUlOKUgOKUgCBsaXR0bGVfd29tZW5fcGFydF8yLnR4dArilJTilIDilIAgd29ya2Zsb3cKICAgIOKUlOKUgOKUgCBTbmFrZWZpbGUKICBgYGAKCgpBIFNuYWtlZmlsZSBpcyBhIHNpbXBsZSBtYXJrZG93biBmaWxlLkJ5IGNvbnZlbnRpb24sIHRoZQpzbmFrZWZpbGUgaXMgbmFtZWQgYFNuYWtlZmlsZWAgYW5kIGxpdmVzIGluIHRoZSBgd29ya2Zsb3dgIHN1Yi1kaXJlY3RvcnkuIApUaGUgY3VycmVudCBTbmFrZWZpbGUgaXMgc2ltcGx5IGEgc3R1YiBiYXNlZCBvbiB0aGUgZWFybGllciBiYXNoIHNjcmlwdDoKCjx0YWJsZSBjbGFzcz0nZmlnJz48dHI+PHRoIGNsYXNzPSdmaWcnPndvcmtmbG93L1NuYWtlZmlsZTwvdGg+PC90cj4KPHRyPjx0ZCBjbGFzcz0nZmlnJz48cHJlPgojIHNwbGl0X2xpbmVzICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiNjYXQgaW5wdXRzL2xpdHRsZV93b21lbl9wYXJ0XzEudHh0IFwKIyAgICB8IHRyIC1jcyAnWzphbHBoYTpdJyAnXG4nIFwKIyAgICA+IDEuc3BsaXRfd29yZHMudHh0CiMgY291bnRfd29yZHMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKI3NvcnQgMS5zcGxpdF93b3Jkcy50eHQgfCB1bmlxIC1jIFwKIyAgICA+IDIuY291bnRfd29yZHMudHh0CiMjIHNvcnRfY291bnQgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKI3NvcnQgLWsxLDFuciAyLmNvdW50X3dvcmRzLnR4dCBcCiMgICAgfCBhd2sgJ0JFR0lOIHtwcmludCAid29yZFx0Y291bnQifSB7IHByaW50ICQyICJcdCIgJDF9JyBcCiMgICAgPiAzLnNvcnRfY291bnRzLnR4dAojIHNlbGVjdF93b3JkcyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiNlZ3JlcCAnXihKb3xBbXl8TGF1cmllfEJldGgpXHMnIDMuc29ydF9jb3VudHMudHh0ID4gNC5zZWxlY3Rfd29yZHMudHh0CjwvcHJlPjwvdGQ+PC90cj48L3RhYmxlPgoKCkEgc25ha2VmaWxlIGlzIGNvbXBvc2VkIG9mIHJ1bGVzOyBlYWNoIHJ1bGUgaGFzIGEgbmFtZSBhbmQgc3BlY2lmaWVzIGRpcmVjdGl2ZXMgc3VjaCBhcwoKLSBpbnB1dCAod2hhdCB0aGUgcnVsZSByZXF1aXJlcykKLSBvdXRwdXQgKHdoYXQgdGhlIHJ1bGUgcHJvbWlzZXMgdG8gZ2VuZXJhdGUpCi0gc2hlbGwgKGhvdyBpdCB3aWxsIG1ha2UgaW5wdXQgaW50byBvdXRwdXQpCgpXZSBhZGQgdGhlIGZpcnN0IHN0ZXAgaW4gdGhlIHdvcmtmbG93IGFzIGEgbmV3IHJ1bGUuIChBZGRpbmcgYmVsb3cgdGhlIGV4aXN0aW5nCmNvbW1lbnQgYmxvY2suKSBOb3RlIHRoYXQgd2Ugd2lsbCBrZWVwIHRoZSBpbnB1dCBmaWxlcyBhbmQgcmVzdWx0IGZpbGVzIGluIApzZXBhcmF0ZSBkaXJlY3Rvcmllcy4KCmBgYApydWxlIHNwbGl0X3dvcmRzOgogICAgaW5wdXQ6ICJpbnB1dHMvbGl0dGxlX3dvbWVuX3BhcnRfMS50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0IiAKICAgIHNoZWxsOiAiY2F0IGlucHV0cy9saXR0bGVfd29tZW5fcGFydF8xLnR4dCB8IHRyIC1jcyAnWzphbHBoYTpdJyAnXG4nID4gcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNwbGl0X3dvcmRzLnR4dCIKYGBgCgojIyMgUnVubmluZyBTbmFrZW1ha2UKCi0gVGhlICoqc25ha2VtYWtlKiogcHl0aG9uIHByb2dyYW0gaW50ZXJwcmV0cyB0aGUgU25ha2VmaWxlIGFuZCBleGVjdXRlcyBydWxlcyBhcyAKYXBwcm9wcmlhdGUgdG8gcHJvZHVjZSB0aGUgZXhwZWN0ZWQgb3V0cHV0LiAKLSBUaGUgc25ha2VtYWtlIHByb2dyYW0gaXMgaW5zdGFsbGVkIGFzIGEgbW9kdWxlIG9uIEdyZWF0IExha2VzLgotIFdoZW4gbGF1bmNoaW5nIHNuYWtlbWFrZSwgWW91IHNwZWNpZnkgdGhlIG51bWJlciBvZiBjb3JlcyAoaS5lLiBDUFVzKSB3aXRoIHRoZQotYyBmbGFnLiBXZSB3aWxsIHVzZSAxIGNvcmUuCgpgYGBzaAptb2R1bGUgbG9hZCBzbmFrZW1ha2UKbW9kdWxlIGxpc3QKIyBjb25maXJtIHlvdSBhcmUgaW4gdGhlIHJpZ2h0IHdvcmtpbmcgZGlyZWN0b3J5CnRyZWUKYGBgCgo+IGBgYHNoPQpDdXJyZW50bHkgTG9hZGVkIE1vZHVsZXM6CiAgMSkgcHl0aG9uMy4xMC1hbmFjb25kYS8yMDIzLjAzICAgMikgc25ha2VtYWtlLzcuMzIuNAokIHRyZWUKLgrilJzilIDilIAgaW5wdXRzCuKUgsKgwqAg4pSc4pSA4pSAIGxpdHRsZV93b21lbl9wYXJ0XzEudHh0CuKUgsKgwqAg4pSU4pSA4pSAIGxpdHRsZV93b21lbl9wYXJ0XzIudHh0CuKUlOKUgOKUgCB3b3JrZmxvdwogICAg4pSU4pSA4pSAIFNuYWtlZmlsZQogIGBgYAoKClJ1biBgc25ha2VtYWtlYCBjb21tYW5kIGFzIGJlbG93LiBOb3RlOgoKLSBgc25ha2VtYWtlYCB3aWxsIGxvb2sgZm9yIGEgU25ha2VmaWxlIGluIHRoZSB3b3JrZmxvdyBkaXJlY3RvcnkuCi0gYHNuYWtlbWFrZWAgZW1pdHMgYSBsb2cgdG8gdGhlIHNjcmVlbiB0aGF0IGluY2x1ZGUgCiAgLSBydW4gZGV0YWlscwogIC0gSm9iIHN0YXRzCiAgLSBSdWxlIGRldGFpbHMKICAtIENvbXBsZXRpb24gc3RhdHVzIGkuZS4gKDEwMCUpIGRvbmUKCmBgYApzbmFrZW1ha2UgLWMxCmBgYAoKCj4gYGBgCiAgICBCdWlsZGluZyBEQUcgb2Ygam9icy4uLgogICAgVXNpbmcgc2hlbGw6IC91c3IvYmluL2Jhc2gKICAgIFByb3ZpZGVkIGNvcmVzOiAxICh1c2UgLS1jb3JlcyB0byBkZWZpbmUgcGFyYWxsZWxpc20pCiAgICBSdWxlcyBjbGFpbWluZyBtb3JlIHRocmVhZHMgd2lsbCBiZSBzY2FsZWQgZG93bi4KICAgIEpvYiBzdGF0czoKICAgIGpvYiAgICAgICAgICAgIGNvdW50CiAgICAtLS0tLS0tLS0tLSAgLS0tLS0tLQogICAgc3BsaXRfd29yZHMgICAgICAgIDEKICAgIHRvdGFsICAgICAgICAgICAgICAxCiAgICAuCiAgICBTZWxlY3Qgam9icyB0byBleGVjdXRlLi4uCiAgICAuCiAgICBbTW9uIEp1biAxMCAxODozNTo0MSAyMDI0XQogICAgcnVsZSBzcGxpdF93b3JkczoKICAgICAgICBpbnB1dDogaW5wdXRzL2xpdHRsZV93b21lbl9wYXJ0XzEudHh0CiAgICAgICAgb3V0cHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0CiAgICAgICAgam9iaWQ6IDAKICAgICAgICByZWFzb246IE1pc3Npbmcgb3V0cHV0IGZpbGVzOiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0CiAgICAgICAgcmVzb3VyY2VzOiB0bXBkaXI9L3RtcAogICAgLgogICAgW01vbiBKdW4gMTAgMTg6MzU6NDEgMjAyNF0KICAgIEZpbmlzaGVkIGpvYiAwLgogICAgMSBvZiAxIHN0ZXBzICgxMDAlKSBkb25lCiAgICBDb21wbGV0ZSBsb2c6IC5zbmFrZW1ha2UvbG9nLzIwMjQtMDYtMTBUMTgzNTQxLjIwNDYzNi5zbmFrZW1ha2UubG9nCiAgYGBgCgpDaGVjayB0aGUgZmlsZSBzeXN0ZW0uIE5vdGUgbmV3IHJlc3VsdHMvIGRpciBhbmQgbmV3IGZpbGUuIE5vdGUgdGhhdCBgc25ha2VtYWtlYApjcmVhdGVkIHRoZSByZXN1bHRzLyBkaXIgYXV0b21hdGljYWxseS4KCmBgYHNoCnRyZWUKYGBgCgo+IGBgYAouCuKUnOKUgOKUgCBpbnB1dHMK4pSCwqDCoCDilJzilIDilIAgbGl0dGxlX3dvbWVuX3BhcnRfMS50eHQK4pSCwqDCoCDilJTilIDilIAgbGl0dGxlX3dvbWVuX3BhcnRfMi50eHQK4pSc4pSA4pSAIHJlc3VsdHMK4pSCwqDCoCDilJTilIDilIAgbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQK4pSU4pSA4pSAIHdvcmtmbG93CiAgICDilJTilIDilIAgU25ha2VmaWxlCiAgYGBgCgpBbHNvIG5vdGUgdGhhdCBgc25ha2VtYWtlYCBjcmVhdGVzIGEgaGlkZGVuIGRpcmVjdG9yeSBgLnNuYWtlbWFrZS9gIGluIHRoZSB3b3JraW5nIApkaXJlY3RvcnkuIFRoaXMgaG9sZHMgdmFyaW91cyBpbmZvIHRoYXQgaGVscCBzbmFrZW1ha2UgbWFuYWdlIHRoZSB3b3JrZmxvdy4gSXQncwpleHRyZW1lbHkgcmFyZSBmb3IgeW91IHRvIG5lZWQgdG8gZXhhbWluZSB0aGUgY29udGVudHMgb2YgdGhlIGAuc25ha2VtYWtlL2AgCmRpcmVjdG9yeSwgYnV0IEZZSS4KCklmIHlvdSByZS1ydW4gYHNuYWtlbWFrZWAgaXQgZGV0ZXJtaW5lcyB0aGUgZmlsZXN5c3RlbSBpcyB1cCB0byBkYXRlIGFuZCBubyAKcnVsZXMgbmVlZCB0byBiZSB0cmlnZ2VyZWQuCgpgYGBzaApzbmFrZW1ha2UgLWMxCmBgYAoKPiBgYGAKQnVpbGRpbmcgREFHIG9mIGpvYnMuLi4KTm90aGluZyB0byBiZSBkb25lIChhbGwgcmVxdWVzdGVkIGZpbGVzIGFyZSBwcmVzZW50IGFuZCB1cCB0byBkYXRlKS4KQ29tcGxldGUgbG9nOiAuc25ha2VtYWtlL2xvZy8yMDI0LTA2LTEwVDE4NDg1MC45NDQ1MzUuc25ha2VtYWtlLmxvZwogIGBgYAoKCiMjIyBBZGQgYSBuZXcgcnVsZQoKSW4gdGhlIFNuYWtlbWFrZSBmaWxlLCBhZGQgdGhlIHNlY29uZCBzdGVwIGFzIGEgbmV3IHJ1bGUgYmVsb3cgdGhlIGZpcnN0IHJ1bGUuCk5vdGUgdGhhdCB0aGUgaW5wdXRzIG9mIHRoaXMgcnVsZSBtYXRjaCB0aGUgb3V0cHV0cyBvZiB0aGUgcHJpb3IgcnVsZS4KCmBgYApydWxlIGNvdW50X3dvcmRzOgogICAgaW5wdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0IgogICAgb3V0cHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLmNvdW50X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAic29ydCByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0IHwgdW5pcSAtYyA+IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5jb3VudF93b3Jkcy50eHQiCmBgYAoKQnkgZGVmYXVsdCwgYHNuYWtlbWFrZWAgb25seSBydW5zIHRoZSBmaXJzdCBydWxlIGluIHRoZSBTbmFrZWZpbGUuIFRvIHRyaWdnZXIgCnRoZSBuZXcgcnVsZSwgeW91IGNhbiBzcGVjaWZ5IHRoZSBkZXNpcmVkIGVuZCB0YXJnZXQgb24gdGhlIGNvbW1hbmQgbGluZS4KCmBgYHNoCnNuYWtlbWFrZSAtYzEgY291bnRfd29yZHMKYGBgCgpOb3RlIHRoZSBsb2cgb3V0cHV0IHNpbWlsYXIgdG8gYWJvdmUsIGJ1dCBub3cgc2hvd3MgdGhlIG5ldyBydWxlLgoKPiBgYGAKQnVpbGRpbmcgREFHIG9mIGpvYnMuLi4KVXNpbmcgc2hlbGw6IC91c3IvYmluL2Jhc2gKUHJvdmlkZWQgY29yZXM6IDEgKHVzZSAtLWNvcmVzIHRvIGRlZmluZSBwYXJhbGxlbGlzbSkKUnVsZXMgY2xhaW1pbmcgbW9yZSB0aHJlYWRzIHdpbGwgYmUgc2NhbGVkIGRvd24uCkpvYiBzdGF0czoKam9iICAgICAgICAgICAgY291bnQKLS0tLS0tLS0tLS0gIC0tLS0tLS0KY291bnRfd29yZHMgICAgICAgIDEKdG90YWwgICAgICAgICAgICAgIDEKLgpTZWxlY3Qgam9icyB0byBleGVjdXRlLi4uCi4KW01vbiBKdW4gMTAgMTg6NTI6MDcgMjAyNF0KcnVsZSBjb3VudF93b3JkczoKICAgIGlucHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0CiAgICBvdXRwdXQ6IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5jb3VudF93b3Jkcy50eHQKICAgIGpvYmlkOiAwCiAgICByZWFzb246IE1pc3Npbmcgb3V0cHV0IGZpbGVzOiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuY291bnRfd29yZHMudHh0CiAgICByZXNvdXJjZXM6IHRtcGRpcj0vdG1wCi4KW01vbiBKdW4gMTAgMTg6NTI6MDggMjAyNF0KRmluaXNoZWQgam9iIDAuCjEgb2YgMSBzdGVwcyAoMTAwJSkgZG9uZQpDb21wbGV0ZSBsb2c6IC5zbmFrZW1ha2UvbG9nLzIwMjQtMDYtMTBUMTg1MjA3LjU5ODY5NS5zbmFrZW1ha2UubG9nCiAgYGBgCgpTZWUgdGhlIG5ldyBmaWxlIGFuZCByZXZpZXcgdGhlIGNvbnRlbnRzOgoKYGBgc2gKaGVhZCByZXN1bHRzLyoKYGBgCgo+IGBgYAo9PT4gcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydDEuY291bnRfd29yZHMudHh0IDw9PQogICAxOTkyIGEKICAgICA3NSBBCiAgICAgIDEgYWJhY2sKICAgICAgMSBhYmFzZQogICAgICAzIGFiYXNoZWQKICAgICAgMSBhYmVkCiAgICAgIDEgYWJqZWN0CiAgICAgIDMgYWJsZQogICAgICAzIGFib21pbmFibGUKICAgICAgMSBhYm9taW5hYmx5Ci4KPT0+IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQgPD09CkNIQVBURVIKT05FClBMQVlJTkcKUElMR1JJTVMKQ2hyaXN0bWFzCndvbgp0CmJlCkNocmlzdG1hcwp3aXRob3V0CiAgYGBgCgojIyMgVmFyaWFibGVzIC8gUnVsZSBvcmRlcmluZwoKRWFjaCBydWxlIGRlZmluZXMgKippbnB1dCoqIGFuZCAqKm91dHB1dCoqIHZhcmlhYmxlczsgeW91IGNhbiB1c2UgdGhvc2UgdmFsdWVzCmluIHRoZSAqKnNoZWxsKiogZGlyZWN0aXZlOgoKYGBgCnJ1bGUgc3BsaXRfd29yZHM6CiAgICBpbnB1dDogImlucHV0cy9saXR0bGVfd29tZW5fcGFydF8xLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQiCiAgICBzaGVsbDogImNhdCB7aW5wdXR9IHwgdHIgLWNzICdbOmFscGhhOl0nICdcbicgPiB7b3V0cHV0fSIKCnJ1bGUgY291bnRfd29yZHM6CiAgICBpbnB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0MS5jb3VudF93b3Jkcy50eHQiCiAgICBzaGVsbDogInNvcnQge2lucHV0fSB8IHVuaXEgLWMgPiB7b3V0cHV0fSIKYGBgCgpTbmFrZW1ha2UgZG9lc24ndCBjYXJlIHdoYXQgb3JkZXIgdGhlIHJ1bGVzIGFwcGVhciBpbiB0aGUgU25ha2VmaWxlIGJ1dCBpdCAKYXNzdW1lcyB0aGUgZmlyc3QgcnVsZSBpcyB0aGUgZGVmYXVsdCB0YXJnZXQuIFB1dCB0aGUgbGFzdCBzdGVwIG9mIHRoZSB3b3JrZmxvdyAKYXQgdGhlIHRvcCBvZiB0aGUgZmlsZSBhbmQgYHNuYWtlbWFrZWAgd2lsbCBkZWZhdWx0IHRvIHJ1bm5pbmcgdGhlIHdob2xlIHdvcmtmbG93LgoKYGBgCnJ1bGUgY291bnRfd29yZHM6CiAgICBpbnB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0MS5jb3VudF93b3Jkcy50eHQiCiAgICBzaGVsbDogInNvcnQge2lucHV0fSB8IHVuaXEgLWMgPiB7b3V0cHV0fSIKCnJ1bGUgc3BsaXRfd29yZHM6CiAgICBpbnB1dDogImlucHV0cy9saXR0bGVfd29tZW5fcGFydF8xLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQiCiAgICBzaGVsbDogImNhdCB7aW5wdXR9IHwgdHIgLWNzICdbOmFscGhhOl0nICdcbicgPiB7b3V0cHV0fSIKYGBgCgpSZXJ1biBhZnRlciBtYWtpbmcgdGhlc2UgY2hhbmdlcywgYW5kIGNoZWNrIG91dCB0aGUgbG9nIChleGNlcHRlZCBiZWxvdykuCgogIC0gTm90ZSB0aGF0IGluICoqSm9iIHN0YXRzKiogYWxsIHJ1bGVzIGFyZSB0cmlnZ2VyZWQuCiAgLSBOb3RlIHRoZSB2YWx1ZSBvZiByZWFzb24gaW4gZWFjaCBvZiB0aGUgcnVsZXMuIERvZXMgdGhlIHJlYXNvbiBtYWtlIHNlbnNlPwoKYGBgc2gKc25ha2VtYWtlIC1jMQpgYGAKCj4gYGBgCi4uLgpKb2Igc3RhdHM6CmpvYiAgICAgICAgICAgIGNvdW50Ci0tLS0tLS0tLS0tICAtLS0tLS0tCmNvdW50X3dvcmRzICAgICAgICAxCnNwbGl0X3dvcmRzICAgICAgICAxCnRvdGFsICAgICAgICAgICAgICAyCi4uLgpydWxlIHNwbGl0X3dvcmRzOgogICAgaW5wdXQ6IGlucHV0cy9saXR0bGVfd29tZW5fcGFydF8xLnR4dAogICAgb3V0cHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0CiAgICBqb2JpZDogMQogICAgcmVhc29uOiBDb2RlIGhhcyBjaGFuZ2VkIHNpbmNlIGxhc3QgZXhlY3V0aW9uCiAgICByZXNvdXJjZXM6IHRtcGRpcj0vdG1wCi4uLgpydWxlIGNvdW50X3dvcmRzOgogICAgaW5wdXQ6IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQKICAgIG91dHB1dDogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydDEuY291bnRfd29yZHMudHh0CiAgICBqb2JpZDogMAogICAgcmVhc29uOiBJbnB1dCBmaWxlcyB1cGRhdGVkIGJ5IGFub3RoZXIgam9iOiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0CiAgICByZXNvdXJjZXM6IHRtcGRpcj0vdG1wCiAgYGBgCgoKIyMjIFVzaW5nIHRyaXBsZSBxdW90ZXMgLyBFc2NhcGluZyBjdXJseSBicmFjZXMKCkFkZCB0aGUgdGhpcmQgc3RlcCBvZiB0aGUgdHJhbnNmb3JtCgotIFBsYWNlIGl0ICoqYWJvdmUqKiB0aGUgZXhpc3RpbmcgcnVsZXMuCi0gVXNlIHtpbnB1dH0gYW5kIHtvdXRwdXR9IHZhcmlhYmxlcy4KLSBXZSB1c2UgdHJpcGxlIHF1b3RlcyB0byBtYWtlIG11bHRpbGluZSBzaGVsbCBjb21tYW5kcy4KLSBJbiB0aGUgc2hlbGwsIGFueSBicmFjZXMgKHt9KSByZWxhdGVkIHRvIHRoZSBjb21tYW5kIG11c3QgYmUgZG91YmxlZCB0byAKZGlzdGluZ3Vpc2ggdGhlbSBmcm9tIFNuYWtlZmlsZSB2YXJpYWJsZXMuCgpgYGAKcnVsZSBzb3J0X2NvdW50czoKICAgIGlucHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLmNvdW50X3dvcmRzLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zb3J0X2NvdW50cy50eHQiCiAgICBzaGVsbDogIiIiCglzb3J0IC1rMSwxbnIge2lucHV0fSBcCgkgICAgfCBhd2sgJ0JFR0lOIHt7cHJpbnQgIndvcmRcdGNvdW50In19IHt7IHByaW50ICQyICJcdCIgJDF9fScgXAogICAgICAgICAgICA+IHtvdXRwdXR9CiAgICAgICAgIiIiCmBgYAoKTm90ZSB0aGF0IHdoZW4geW91IHJ1biBgc25ha2VtYWtlYCBpdCBvbmx5IGdlbmVyYXRlcyB0aGUgZmlsZXMgaXQgbmVlZHMgdG8uCgpgYGBzaApzbmFrZW1ha2UgLWMxCmBgYAoKPiBgYGAKLi4uCkpvYiBzdGF0czoKam9iICAgICAgICAgICAgY291bnQKLS0tLS0tLS0tLS0gIC0tLS0tLS0Kc29ydF9jb3VudHMgICAgICAgIDEKdG90YWwgICAgICAgICAgICAgIDEKLi4uCnJ1bGUgc29ydF9jb3VudHM6CiAgICBpbnB1dDogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLmNvdW50X3dvcmRzLnR4dAogICAgb3V0cHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0CiAgICBqb2JpZDogMAogICAgcmVhc29uOiBNaXNzaW5nIG91dHB1dCBmaWxlczogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNvcnRfY291bnRzLnR4dAogICAgcmVzb3VyY2VzOiB0bXBkaXI9L3RtcAouLi4KMSBvZiAxIHN0ZXBzICgxMDAlKSBkb25lCiAgYGBgClVzZSB0cmlwbGUgcXVvdGVzIHRvIGNyZWF0ZSBtdWx0aWxpbmUgc2hlbGwgY29tbWFuZHMuIElmIHlvdXIgY29tbWFuZHMgdXNlIGN1cmx5IGJyYWNlcyAoe30pIHlvdSBuZWVkIHRvIGRvdWJsZSB0aGVtIHNvIHNuYWtlbWFrZSBkb2Vzbid0IGdldCBjb25mdXNlZC4KCiMjIyBTbmFrZW1ha2UgZHJ5LXJ1biAvIEludGVycHJldGluZyBydWxlIGVycm9ycwoKQWRkIHRoZSBmb3VydGggcnVsZSwgcGxhY2luZyBpdCBhYm92ZSB0aGUgcHJpb3IuCgpgYGAKcnVsZSBzZWxlY3Rfd29yZHM6CiAgICBpbnB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zb3J0X2NvdW50cy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc2VsZWN0X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAiZWdyZXAgJ14oSm98QW15fExhdXJpZXxCZXRoKVxzJyB7aW5wdXR9ID4ge291dHB1dH0iCmBgYAoKWW91IHByb2JhYmx5IGhhdmUgbm90aWNlZCB0aGF0IGBzbmFrZW1ha2VgIHdpbGwgb25seSBydW4gdGhlIHJ1bGVzIGl0IG5lZWRzIHRvLCAKYmFzZWQgb24gY2hhbmdlcyBpbiBmaWxlcyBvciBzY3JpcHRzIG9yIG1pc3NpbmcgZmlsZXMuIFlvdSBjYW4gcHJldmlldyB3aGF0IApgc25ha2VtYWtlYCB3aWxsIGRvIGJ5IHVzaW5nIHRoZSBkcnktcnVuIGZsYWcgYC0tZHJ5LXJ1bmAgb3IgYC1uYC4gVGhpcyB3aWxsCnNob3cgdGhlIHR5cGljYWwgc25ha2VtYWtlIG91dHB1dCBidXQgd2lsbCBub3QgY2hhbmdlIGFueSBmaWxlcy4KCmBgYHNoCnNuYWtlbWFrZSAtYzEgLS1kcnktcnVuCmBgYAoKPiBgYGAKQnVpbGRpbmcgREFHIG9mIGpvYnMuLi4KSm9iIHN0YXRzOgpqb2IgICAgICAgICAgICAgY291bnQKLS0tLS0tLS0tLS0tICAtLS0tLS0tCnNlbGVjdF93b3JkcyAgICAgICAgMQp0b3RhbCAgICAgICAgICAgICAgIDEKLgpbTW9uIEp1biAxMCAxOTo0MTo0MyAyMDI0XQpydWxlIHNlbGVjdF93b3JkczoKICAgIGlucHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0CiAgICBvdXRwdXQ6IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zZWxlY3Rfd29yZHMudHh0CiAgICBqb2JpZDogMAogICAgcmVhc29uOiBNaXNzaW5nIG91dHB1dCBmaWxlczogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNlbGVjdF93b3Jkcy50eHQKICAgIHJlc291cmNlczogdG1wZGlyPS90bXAKLgpKb2Igc3RhdHM6CmpvYiAgICAgICAgICAgICBjb3VudAotLS0tLS0tLS0tLS0gIC0tLS0tLS0Kc2VsZWN0X3dvcmRzICAgICAgICAxCnRvdGFsICAgICAgICAgICAgICAgMQouClJlYXNvbnM6CiAgICAoY2hlY2sgaW5kaXZpZHVhbCBqb2JzIGFib3ZlIGZvciBkZXRhaWxzKQogICAgbWlzc2luZyBvdXRwdXQgZmlsZXM6CiAgICAgICAgc2VsZWN0X3dvcmRzCi4KVGhpcyB3YXMgYSBkcnktcnVuIChmbGFnIC1uKS4gVGhlIG9yZGVyIG9mIGpvYnMgZG9lcyBub3QgcmVmbGVjdCB0aGUgb3JkZXIgb2YgZXhlY3V0aW9uLgogIGBgYAoKRGV2ZWxvcGluZyBhIFNuYWtlZmlsZSB0eXBpY2FsbHkgaW52b2x2ZXMgc29tZSB0cm91Ymxlc2hvb3RpbmcgYWxvbmcgdGhlIHdheS4KSSd2ZSBpbnRlbnRpb25hbGx5IGludHJvZHVjZWQgYSB0eXBvIGluIHRoZSBTbmFrZWZpbGUgYW5kIHRoZW4gZGlkIGEgZHJ5LXJ1biB0bwpzaG93IGFuIFNuYWtlZmlsZSBlcnJvciBtZXNzYWdlOgoKPiBgYGAKJCBzbmFrZW1ha2UgLWMxIC0tZHJ5LXJ1bgouCkEgIEJ1aWxkaW5nIERBRyBvZiBqb2JzLi4uCkIgIE1pc3NpbmdJbnB1dEV4Y2VwdGlvbiBpbiBydWxlIHNlbGVjdF93b3JkcyBpbiBmaWxlIC4uLi9TbmFrZWZpbGUsIGxpbmUgMTU6CkMgIE1pc3NpbmcgaW5wdXQgZmlsZXMgZm9yIHJ1bGUgc2VsZWN0X3dvcmRzOgpEICAgIG91dHB1dDogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNlbGVjdF93b3Jkcy50eHQKRSAgICBhZmZlY3RlZCBmaWxlczoKRiAgICAgICAgcmVzdWx0L2xpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0CiAgYGBgCgpOb3RlOgoKLSBCIGxpbmUgbnVtYmVyIChsaW5lIDE1KSAKLSBDIE1pc3NpbmcgaW5wdXQgZmlsZXMgZm9yIHJ1bGUgc2VsZWN0X3dvcmRzCi0gRSAmIEYgcmVzdWx0L2xpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0CgpDYW4geW91IHNlZSB0aGUgZXJyb3I/IFlvdSB3aWxsIGxpa2VseSBuZWVkIHRvIGNvbnNpZGVyIHRoZSBydWxlIGluIGNvbnRleHQ6CgpgYGAKcnVsZSBzZWxlY3Rfd29yZHM6CiAgICBpbnB1dDogInJlc3VsdC9saXR0bGVfd29tZW5fcGFydF8xLnNvcnRfY291bnRzLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zZWxlY3Rfd29yZHMudHh0IgogICAgc2hlbGw6ICJlZ3JlcCAnXihKb3xBbXl8TGF1cmllfEJldGgpXHMnIHtpbnB1dH0gPiB7b3V0cHV0fSIKCnJ1bGUgc29ydF9jb3VudHM6CiAgICBpbnB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5jb3VudF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0IgogICAgc2hlbGw6ICIiIgoJc29ydCAtazEsMW5yIHtpbnB1dH0gXAoJICAgIHwgYXdrICdCRUdJTiB7e3ByaW50ICJ3b3JkXHRjb3VudCJ9fSB7eyBwcmludCAkMiAiXHQiICQxfX0nIFwKICAgICAgICAgICAgPiB7b3V0cHV0fQogICAgICAgICIiIgpydWxlIGNvdW50X3dvcmRzOgogICAgaW5wdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc3BsaXRfd29yZHMudHh0IgogICAgb3V0cHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLmNvdW50X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAic29ydCB7aW5wdXR9IHwgdW5pcSAtYyA+IHtvdXRwdXR9IgoKcnVsZSBzcGxpdF93b3JkczoKICAgIGlucHV0OiAiaW5wdXRzL2xpdHRsZV93b21lbl9wYXJ0XzEudHh0IgogICAgb3V0cHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNwbGl0X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAiY2F0IHtpbnB1dH0gfCB0ciAtY3MgJ1s6YWxwaGE6XScgJ1xuJyA+IHtvdXRwdXR9IgpgYGAKCkZpeCB0aGUgdHlwbyBhbmQgY29uZmlybSB0aGUgZml4IHVzaW5nIGAtLWRyeS1ydW5gIHRvIGdldCBhIGNsZWFuIG91dHB1dC4gVGhlbgpleGVjdXRlIGBzbmFrZW1ha2VgIG5vcm1hbGx5IHVudGlsIHlvdSBhcmUgMTAwJSBkb25lLiAKCiMjIyBWaXN1YWxpemluZyB0aGUgREFHIC8gcnVsZWdyYXBoCgpJbiBhZGRpdGlvbiB0byBkb2luZyBhIGRyeSBydW4sIGBzbmFrZW1ha2VgIGNhbiBnZW5lcmF0ZSBhIGRpYWdyYW0gb2YgdGhlIHJ1bGVzCnRvIGV4ZWN1dGUuIFNuYWtlbWFrZSB0aGlua3Mgb2YgdGhlIHJ1bGVzIGFzIGEgW0RpcmVjdGVkIEFjeWNsaWMKR3JhcGhdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RpcmVjdGVkX2FjeWNsaWNfZ3JhcGgpe3RhcmdldD0iX2JsYW5rIn0KKERBRykuIFlvdSBjYW4gc2VlIHRoaXMgd2l0aCB0aGUgYC0tZGFnYCBmbGFnIGFuZCB0aGUgYGRvdGAgcHJvZ3JhbSAocGFydCBvZgpbR3JhcGh2aXpdKGh0dHBzOi8vZ3JhcGh2aXoub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSwgYW4gb3BlbiBzb3VyY2UgZ3JhcGgKdmlzdWFsaXplcikuCgpgYGBzaApzbmFrZW1ha2UgLS1kYWcgfCBkb3QgLVRwbmcgPiBkYWcucG5nCmBgYAoKIVtkYWcucG5nXShpbWFnZXMvTW9kdWxlMDdfZGFnLnBuZykKPGJyLz4KCkFzIHlvdSBjYW4gc2VlIGFib3ZlLCBieSBkZWZhdWx0IGBkb3RgIGRpc3BsYXlzIHRoZSBncmFwaHMgdmVydGljYWxseSB3aXRoIGlucHV0cwphdCB0aGUgdG9wIGFuZCAgb3V0cHV0cyBhdCB0aGUgYm90dG9tLiBPdXIgU25ha2VmaWxlIGlzIGFycmFuZ2VkIGZyb20gCm91dHB1dCB0byBpbnB1dCwgc28geW91IGNhbiBpbnZlcnQgdGhlIGRpYWdyYW0gbGlrZSBzbzoKCmBgYHNoCnNuYWtlbWFrZSAtLWRhZyB8IGRvdCAtR3JhbmtkaXI9IkJUIiAtVHBuZyA+IGRhZ19pbnZlcnRlZC5wbmcKYGBgCgohW2RhZ19pbnZlcnRlZC5wbmddKGltYWdlcy9Nb2R1bGUwN19kYWdfaW52ZXJ0ZWQucG5nKQo8YnIvPgoKIyMjIEFsbCB0YXJnZXQgLyBXaWxkY2FyZHMgLyBWaXN1YWxpemFpbmcgdGhlIHJ1bGVncmFwaAoKVG8gZW1waGFzaXplIHRoZSBlbmRwb2ludCBvZiB0aGUgd29ya2Zsb3cgYW5kIGFsc28gY2xhcmlmeSB0aGUgZGVmYXVsdCB0YXJnZXQsCml0J3MgY29tbW9uIHRvIHBsYWNlIGEgInJ1bGUgYWxsIiBhdCB0aGUgdG9wIG9mIHRoZSBmaWxlLiAoVGhpcyBhbHNvIGFsbG93cyB5b3UKdG8gcmVvcmRlciBydWxlcyB3aXRoaW4gdGhlIGZpbGUgYXJiaXRyYXJpbHkuKSBJdCBpcyB1bnVzdWFsIGluIHRoYXQgaXQgb25seSBoYXMKYW4gaW5wdXQgZGlyZWN0aXZlLgoKYGBgCnJ1bGUgYWxsOgogICAgaW5wdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc2VsZWN0X3dvcmRzLnR4dCIKYGBgCgpTbmFrZW1ha2UgKndpbGRjYXJkcyogbWFrZSBpdCBlYXN5IHRvIHRvIGV4dGVuZCB0aGUgd29ya2Zsb3cgdG8gbmV3IGlucHV0cy4gKEFuZAp0aGV5IHR5cGljYWxseSByZWR1Y2UgcmVwZXRpdGlvbiBvZiBmaWxlIG5hbWVzLikgSGVyZSBpcyBhbiBleGNlcnB0IG9mIHRoZSAKU25ha2VmaWxlIHNob3dpbmcgdGhlIG9yaWdpbmFsICoqc2VsZWN0X3dvcmRzKiogcnVsZSBhbmQgdGhlIHNhbWUgcnVsZSB3aXRoIAp3aWxkY2FyZHMuCgpgYGAKcnVsZSBhbGw6CiAgICBpbnB1dDogInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zZWxlY3Rfd29yZHMudHh0IgoKIyBvcmlnaW5hbCBydWxlCiMgcnVsZSBzZWxlY3Rfd29yZHM6CiMgICAgaW5wdXQ6ICJyZXN1bHQvbGl0dGxlX3dvbWVuX3BhcnRfMS5zb3J0X2NvdW50cy50eHQiCiMgICAgb3V0cHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNlbGVjdF93b3Jkcy50eHQiCiMgICAgc2hlbGw6ICJlZ3JlcCAnXihKb3xBbXl8TGF1cmllfEJldGgpXHMnIHtpbnB1dH0gPiB7b3V0cHV0fSIKCiMgbm93IHdpdGggd2lsZGNhcmRzCnJ1bGUgc2VsZWN0X3dvcmRzOgogICAgaW5wdXQ6ICJyZXN1bHQve2Jhc2VfbmFtZX0uc29ydF9jb3VudHMudHh0IgogICAgb3V0cHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zZWxlY3Rfd29yZHMudHh0IgogICAgc2hlbGw6ICJlZ3JlcCAnXihKb3xBbXl8TGF1cmllfEJldGgpXHMnIHtpbnB1dH0gPiB7b3V0cHV0fSIKCmBgYApIb3cgZG9lcyB0aGlzIHdvcms/CgotIFRoZSAqKmFsbCoqIHJ1bGUgbmVlZHMgYW4gaW5wdXQgbmFtZWQgInJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zZWxlY3Rfd29yZHMudHh0IgotIFRoZSAqKnNlbGVjdF93b3JkcyoqIHJ1bGUgY2FuIHByb2R1Y2UgInJlc3VsdHMve2Jhc2VfbmFtZX0uc2VsZWN0X3dvcmRzLnR4dCIgd2hpY2ggd291bGQgbWF0Y2ggdGhlICoqYWxsKiogaW5wdXQgaWYgdGhlIHdpbGRjYXJkICp7YmFzZV9uYW1lfSo9ImxpdHRsZV93b21lbl9wYXJ0XzEiCi0gSW4gKipzZWxlY3Rfd29yZCoqIGlucHV0LCB0aGUgcnVsZSBzdWJzdGl0dXRlcyB0aGUgKntiYXNlX25hbWV9KiB2YWx1ZSBmb3IgaXQncyB3aWxkY2FyZCwgbWFraW5nICJyZXN1bHQvbGl0dGxlX3dvbWVuX3BhcnRfMS5zb3J0X2NvdW50cy50eHQiCi0gU28gdGhlIHRoZSB2YWx1ZSBvZiB0aGUgd2lsZGNhcmQgaXMgZGV0ZXJtaW5lZCBieSB0aGUgZmlsZSBuYW1lIGluIHRoZSBhbGwgcnVsZSBhbmQgdGhhdCB2YWx1ZSBwcm9wYWdhdGVzIGZyb20gb3V0cHV0IHRvIGlucHV0LgoKV2hhdCBoYXBwZW5zIGlmIHdlIHJ1biBzbmFrZW1ha2U/CgpgYGBzaApzbmFrZW1ha2UgLWMxIC0tZHJ5LXJ1bgpgYGAKCj4gYGBgCkJ1aWxkaW5nIERBRyBvZiBqb2JzLi4uCk5vdGhpbmcgdG8gYmUgZG9uZSAoYWxsIHJlcXVlc3RlZCBmaWxlcyBhcmUgcHJlc2VudCBhbmQgdXAgdG8gZGF0ZSkuCiAgYGBgCgpBbmQgdGhhdCBhY3R1YWxseSBtYWtlcyBzZW5zZTogZXZlbiB0aG91Z2ggd2UgdXNlZCB3aWxkY2FyZHMsIHRoZSBydWxlcyBlbmRlZCB1cAp3aXRoIGlkZW50aWNhbCBpbnB1dCBhbmQgb3V0cHV0IHZhbHVlcy4KCldlIGNhbiBleHRlbmQgd2lsZGNhcmRzIGFjcm9zcyByZW1haW5pbmcgcnVsZXMgYXMgYmVsb3cuIE5vdGUgdGhhdCB3ZSBuZWVkIHRvIAprZWVwIHRoZSBsaXRlcmFsIHZhbHVlIGluIHRoZSAqKmFsbCoqIHJ1bGUgc28gdGhlIG90aGVyIHJ1bGVzIGNhbiBkZXJpdmUgdGhlCnZhbHVlIG9mIHRoZSB3aWxkY2FyZC4KCmBgYApydWxlIGFsbDoKICAgIGlucHV0OiAicmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNlbGVjdF93b3Jkcy50eHQiCgpydWxlIHNlbGVjdF93b3JkczoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zb3J0X2NvdW50cy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNlbGVjdF93b3Jkcy50eHQiCiAgICBzaGVsbDogImVncmVwICdeKEpvfEFteXxMYXVyaWV8QmV0aClccycge2lucHV0fSA+IHtvdXRwdXR9IgoKcnVsZSBzb3J0X2NvdW50czoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5jb3VudF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNvcnRfY291bnRzLnR4dCIKICAgIHNoZWxsOiAiIiIKCXNvcnQgLWsxLDFuciB7aW5wdXR9IFwKCSAgICB8IGF3ayAnQkVHSU4ge3twcmludCAid29yZFx0Y291bnQifX0ge3sgcHJpbnQgJDIgIlx0IiAkMX19JyBcCiAgICAgICAgICAgID4ge291dHB1dH0KICAgICAgICAiIiIKcnVsZSBjb3VudF93b3JkczoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zcGxpdF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LmNvdW50X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAic29ydCB7aW5wdXR9IHwgdW5pcSAtYyA+IHtvdXRwdXR9IgoKcnVsZSBzcGxpdF93b3JkczoKICAgIGlucHV0OiAiaW5wdXRzL3tiYXNlX25hbWV9LnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMve2Jhc2VfbmFtZX0uc3BsaXRfd29yZHMudHh0IgogICAgc2hlbGw6ICJjYXQge2lucHV0fSB8IHRyIC1jcyAnWzphbHBoYTpdJyAnXG4nID4ge291dHB1dH0iCmBgYAoKTm93IHRoYXQgdGhlIFNuYWtlZmlsZSBpcyB1c2luZyB3aWxkY2FyZHMsIGl0IGlzIHRyaXZpYWwgdG8gZXh0ZW5kIGl0IHRvIG5ldyAKaW5wdXRzLiBXZSBhZGp1c3QgdGhlICoqYWxsKiogcnVsZSBhZGRpbmcgYSBzZWNvbmQgdmFsdWUsIHNlcGFyYXRlZCBieSBhIGNvbW1hOgoKYGBgCnJ1bGUgYWxsOgogICAgaW5wdXQ6ICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc2VsZWN0X3dvcmRzLnR4dCIsICJyZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzIuc2VsZWN0X3dvcmRzLnR4dCIKLi4uCmBgYAoKVGhlIGFkZGl0aW9uIG9mIGEgbmV3IHdpbGRjYXJkIHZhbHVlIChsaXR0bGVfd29tZW5fcGFydF8yKSB3aWxsIHRyaWdnZXIgYWxsIGZpdmUKcnVsZXMuCgpgYGBzaApzbmFrZW1ha2UgLS1kcnktcnVuCmBgYAoKPiBgYGAKLi4uCkpvYiBzdGF0czoKam9iICAgICAgICAgICAgIGNvdW50Ci0tLS0tLS0tLS0tLSAgLS0tLS0tLQphbGwgICAgICAgICAgICAgICAgIDEKY291bnRfd29yZHMgICAgICAgICAxCnNlbGVjdF93b3JkcyAgICAgICAgMQpzb3J0X2NvdW50cyAgICAgICAgIDEKc3BsaXRfd29yZHMgICAgICAgICAxCnRvdGFsICAgICAgICAgICAgICAgNQouLi4KICBgYGAKCkJlZm9yZSB3ZSBydW4gdGhpcyBmb3IgcmVhbCwgbGV0J3MgYWRkIGEgbmV3IGZsYWcgYC0tZm9yY2VhbGxgIHRvIHRlbGwgc25ha2VtYWtlCnRvIHRyaWdnZXIgZXZlcnkgc2luZ2xlIHJ1bGUgZm9yIGFsbCBwb3NzaWJsZSBpbnB1dHMuIEJhc2ljYWxseSBhY3QgbGlrZSB0aGVyZSAKYXJlIG5vIGV4aXN0aW5nIG91dHB1dHMgYW5kIGl0IG5lZWRzIHRvIHN0YXJ0IGZyb20gc2NyYXRjaC4gQnV0IHRoaXMgaXMgc3RpbGwKYSBkcnktcnVuLCBzbyBubyBmaWxlcyB3aWxsIGJlIGNoYW5nZWQuCgpgYGBzaApzbmFrZW1ha2UgLS1kcnktcnVuIC0tZm9yY2VhbGwKYGBgCgo+IGBgYAouLi4KSm9iIHN0YXRzOgpqb2IgICAgICAgICAgICAgY291bnQKLS0tLS0tLS0tLS0tICAtLS0tLS0tCmFsbCAgICAgICAgICAgICAgICAgMQpjb3VudF93b3JkcyAgICAgICAgIDIKc2VsZWN0X3dvcmRzICAgICAgICAyCnNvcnRfY291bnRzICAgICAgICAgMgpzcGxpdF93b3JkcyAgICAgICAgIDIKdG90YWwgICAgICAgICAgICAgICA5Ci4uLgogIGBgYAoKVHJ5IGdlbmVyYXRpbmcgYSBEQUcgdmlzdWFsaXphdGlvbi4gKE5vdyBpdCdzIHRydWx5IGxvb2tpbmcgbW9yZSBsaWtlIGEgCmdyYXBoLikgTm90ZToKCi0gRWFjaCB3aWxkY2FyZCB2YWx1ZSBnZXRzIGl0J3Mgb3duIHBhdGgKLSBUaGUgd2lsZGNhcmQgdmFsdWVzIGFyZSBkaXNwbGF5ZWQgYXQgdGhlIGlucHV0Ci0gU29tZSBib3ggb3V0bGluZXMgYXJlIHNvbGlkIGFuZCBvdGhlciBhcmUgZG90dGVkOyB3aHkgaXMgdGhhdD8KCmBgYHNoCnNuYWtlbWFrZSAtLWRhZyB8IGRvdCAtR3JhbmtkaXI9IkJUIiAtVHBuZyA+IGRhZ19pbnZlcnRlZF93aWxkY2FyZHMucG5nCmBgYAoKIVtkYWdfaW52ZXJ0ZWRfd2lsZGNhcmRzLnBuZ10oaW1hZ2VzL01vZHVsZTA3X2RhZ19pbnZlcnRlZF93aWxkY2FyZHMucG5nKQo8YnIvPgoKV2l0aCBjb21wbGV4IHdvcmtmbG93cywgeW91IHNvbWV0aW1lcyBkb24ndCB3YW50IHRvIHNlZSBhbGwgdGhlIHBvc3NpYmxlIGlucHV0cyAKYW5kIHdvdWxkIHJhdGhlciBqdXN0IHNlZSB0aGUgYWJzdHJhY3QgcmVsYXRpb25zaGlwIG9mIHRoZSBydWxlcy4gU25ha2VtYWtlCnN1cHBvcnRzIHRoaXMgd2l0aCBhIHJlbGF0ZWQgZmxhZyBgLS1ydWxlZ3JhcGhgCgpgYGBzaApzbmFrZW1ha2UgLS1ydWxlZ3JhcGggfCBkb3QgLUdyYW5rZGlyPSJCVCIgLVRwbmcgPiBydWxlZ3JhcGgucG5nCmBgYAoKIVtydWxlZ3JhcGgucG5nXShpbWFnZXMvTW9kdWxlMDdfcnVsZWdyYXBoLnBuZykKPGJyLz4KCgpPay4gR28gYWhlYWQgYW5kIHJ1biBgc25ha2VtYWtlYCB1bnRpbCB5b3UgYXJlIDEwMCUgY29tcGxldGUgYW5kIHNlZSB0aGVzZSBmaWxlcyBpbiAKcmVzdWx0czoKCj4gYGBgCiQgbHMgcmVzdWx0cwpsaXR0bGVfd29tZW5fcGFydF8xLmNvdW50X3dvcmRzLnR4dCAgIGxpdHRsZV93b21lbl9wYXJ0XzIuY291bnRfd29yZHMudHh0CmxpdHRsZV93b21lbl9wYXJ0XzEuc2VsZWN0X3dvcmRzLnR4dCAgbGl0dGxlX3dvbWVuX3BhcnRfMi5zZWxlY3Rfd29yZHMudHh0CmxpdHRsZV93b21lbl9wYXJ0XzEuc29ydF9jb3VudHMudHh0ICAgbGl0dGxlX3dvbWVuX3BhcnRfMi5zb3J0X2NvdW50cy50eHQKbGl0dGxlX3dvbWVuX3BhcnRfMS5zcGxpdF93b3Jkcy50eHQgICBsaXR0bGVfd29tZW5fcGFydF8yLnNwbGl0X3dvcmRzLnR4dAogIGBgYAoKCiMjIyBUaGUgZXhwYW5kIGZ1bmN0aW9uIC8gY29uZmlnIGZpbGVzIC8gcGFyYW1ldGVycwoKSWRlYWxseSwgYSB3b3JrZmxvdyBjb3VsZCBiZSBydW4gb24gYSBuZXcgc2V0IG9mIGlucHV0cyB3aXRob3V0IGNoYW5naW5nIHRoZQpTbmFrZWZpbGUgYXQgYWxsLiBGb3IgZXhhbXBsZSwgd2Ugc2hvdWxkIGJlIGFibGUgdG8gdXNlIG91ciB1bm1vZGlmaWVkIFNuYWtlZmlsZSAKd2l0aCB0aGUgaW5wdXQgb2Ygb2YgU2hha2VzcGVhcmUncyBbUm9tZW8gYW5kCkp1bGlldF0oaHR0cHM6Ly93d3cuZ3V0ZW5iZXJnLm9yZy9lYm9va3MvMTUxMykgdG8gc2VlIHdoZXRoZXIgTW9udGFndWUgaXMKbWVudGlvbmVkIG1vcmUgdGhhbiBDYXB1bGV0LiBNb3N0IG9mIHRoZSBydWxlcyBpbiB0aGUgd29ya2Zsb3cgYXJlIGFscmVhZHkgCmdlbmVyYWwgZW5vdWdoOyBidXQgdGhlcmUgYXJlIHR3byBleGNlcHRpb25zOgoKLSBUaGUgKiphbGwqKiBydWxlIG1lbnRpb25zIHNwZWNpZmljIHRleHRzLgotIFRoZSAqKnNlbGVjdF93b3JkcyoqIHJ1bGUgbWVudGlvbnMgc3BlY2lmaWMgbmFtZXMuCgpXZSBjYW4gZXh0cmFjdCB0aGVzZSBzcGVjaWZpY3MgZnJvbSB0aGUgU25ha2VmaWxlIHVzaW5nIGEgCipjb25maWd1cmF0aW9uIGZpbGUqLiBXZSdsbCBzdGFydCB3aXRoIHRoZSAqKnNlbGVjdF93b3JkcyoqIHJ1bGUuCgoKYGBgCnJ1bGUgc2VsZWN0X3dvcmRzOgogICAgaW5wdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNvcnRfY291bnRzLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMve2Jhc2VfbmFtZX0uc2VsZWN0X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAiZWdyZXAgJ14oSm98QW15fExhdXJpZXxCZXRoKVxzJyB7aW5wdXR9ID4ge291dHB1dH0iCmBgYAoKRmlyc3RseSwgd2UnbGwgcHVsbCB0aGUgcmVndWxhciBleHByZXNzaW9uIG91dCBvZiB0aGUgc2hlbGwgZGlyZWN0aXZlIGludG8gYQpbY29uZmlndXJhdGlvbgpmaWxlXShodHRwczovL3NuYWtlbWFrZS5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvc25ha2VmaWxlcy9jb25maWd1cmF0aW9uLmh0bWwjY29uZmlndXJhdGlvbil7dGFyZ2V0PSJfYmxhbmsifS4KU25ha2VtYWtlIHN1cHBvcnRzIHNldmVyYWwgc3R5bGVzIG9mIGNvbmZpZ3VyYXRpb24gZmlsZTsgd2UnbGwgdXNlIHRoZSBZQU1MCnN5bnRheC4gQnkgY29udmVudGlvbiB3ZSdsbCBjcmVhdGUgY29uZmlnL2NvbmZpZy55YW1sOyB5b3UgY291bGQgY3JlYXRlIGFuZCBlZGl0CnRoaXMgZmlsZSBtYW51YWxseTsgYWx0ZXJuYXRpdmVseSB5b3UgY2FuIGFsc28gZXhlY3V0ZSB0aGUgbGluZSBiZWxvdzoKCmBgYHNoCm1rZGlyIGNvbmZpZwplY2hvICdzZWxlY3Rfd29yZHNfcmVnZXg6IF4oSm98QW15fExhdXJpZXxCZXRoKVxzJyA+IGNvbmZpZy9jb25maWcueWFtbApjYXQgY29uZmlnL2NvbmZpZy55YW1sCmBgYAoKTm93IGFkZCB0aGlzIGxpbmUgYWJvdmUgdGhlICoqYWxsKiogcnVsZToKCmBgYApjb25maWdmaWxlOiAiY29uZmlnL2NvbmZpZy55YW1sIgpgYGAKCkFkZCBhIHBhcmFtZXRlciBpbml0aWFsaXplZCB0byB0aGUgY29uZmlnIGtleSAqc2VsZWN0X3dvcmRzX3JlZ2V4Ki4gVGhlbiBhZGp1c3QKdGhlIHNlbGVjdF93b3JkcyBydWxlIHRvIHJlZmVybmNlIHRoYXQgcGFyYW1ldGVyLgoKYGBgCnJ1bGUgc2VsZWN0X3dvcmRzOgogICAgaW5wdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNvcnRfY291bnRzLnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMve2Jhc2VfbmFtZX0uc2VsZWN0X3dvcmRzLnR4dCIKICAgIHBhcmFtczogcmVnZXg9Y29uZmlnW3NlbGVjdF93b3Jkc19yZWdleF0KICAgIHNoZWxsOiAiZWdyZXAgJ3twYXJhbXMucmVnZXh9JyB7aW5wdXR9ID4ge291dHB1dH0iCmBgYAoKVGhlIFtwYXJhbXMKZGlyZWN0aXZlXShodHRwczovL3NuYWtlbWFrZS5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvc25ha2VmaWxlcy9ydWxlcy5odG1sI25vbi1maWxlLXBhcmFtZXRlcnMtZm9yLXJ1bGVzKXt0YXJnZXQ9Il9ibGFuayJ9CmlzIGEgd2F5IG9mIGFkanVzdGluZyB0aGUgc2hlbGwgY29tbWFuZCB3aXRoIGEgdmFsdWUgdGhhdCBpc24ndCB0aWVkIHRvIHRoZQpmaWxlc3lzdGVtLiBBbHNvIG5vdGUgdGhhdCBwYXJhbXMgY2FuIGJlIG5hbWVkIChlLmcuIGB4PSJ2YWx1ZSJgKSBhbmQgcmVmZXJlbmNlZApieSBuYW1lIGluIHRoZSBzaGVsbCAoYHtwYXJhbXMueH1gKS4KCk5vdyB0aGF0IHdlJ3ZlIG1hZGUgdGhpcyBjaGFuZ2UsIGxldCdzIGRyeS1ydW4gc25ha2VtYWtlLiBOb3RlIHdlJ3JlIGFkZGluZyB0aGUKYC1wYCBmbGFnIHNvIHNuYWtlbWFrZSB3aWxsIHByaW50IG91dCB0aGUgYWN0dWFsIGNvbW1hbmRzIGl0IHdpbGwgZXhlY3V0ZToKCmBgYHNoCnNuYWtlbWFrZSAtLWRyeS1ydW4gLXAKYGBgCgpJbiB0aGUgb3V0cHV0LCBub3RlOgoKLSBJbiBKb2Igc3RhdHMsIHdlIHNlZSBzbmFrZW1ha2UgYmVsaWV2ZXMgd2UgbmVlZCB0byByZXJ1biAqKnNlbGVjdF93b3JkcyoqIGZvciBib3RoIGlucHV0cy4KLSBJbiB0aGUgcnVsZSBkZXRhaWxzLCB0aGUgcmVhc29uIHRoZSBydWxlIGlzIHRyaWdnZXJlZCBpcyB0aGF0IGNvZGUgYW5kIHBhcmFtcyBoYXZlIGNoYW5nZWQKLSBCZWNhdXNlIHdlIHVzZWQgYC1wYCwgeW91IGNhbiBzZWUgdGhlIGFjdHVhbCBlZ3JlcCBzdGF0ZW1lbnQgKHdoaWNoIGxvb2tzIGZpbmUpLgoKPiBgYGAKQnVpbGRpbmcgREFHIG9mIGpvYnMuLi4KSm9iIHN0YXRzOgpqb2IgICAgICAgICAgICAgY291bnQKLS0tLS0tLS0tLS0tICAtLS0tLS0tCmFsbCAgICAgICAgICAgICAgICAgMQpzZWxlY3Rfd29yZHMgICAgICAgIDIKdG90YWwgICAgICAgICAgICAgICAzCi4KW01vbiBKdW4gMTAgMjM6MDE6MjEgMjAyNF0KcnVsZSBzZWxlY3Rfd29yZHM6CiAgICBpbnB1dDogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNvcnRfY291bnRzLnR4dAogICAgb3V0cHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzEuc2VsZWN0X3dvcmRzLnR4dAogICAgam9iaWQ6IDEKICAgIHJlYXNvbjogQ29kZSBoYXMgY2hhbmdlZCBzaW5jZSBsYXN0IGV4ZWN1dGlvbjsgUGFyYW1zIGhhdmUgY2hhbmdlZCBzaW5jZSBsYXN0IGV4ZWN1dGlvbgogICAgd2lsZGNhcmRzOiBiYXNlX25hbWU9bGl0dGxlX3dvbWVuX3BhcnRfMQogICAgcmVzb3VyY2VzOiB0bXBkaXI9L3RtcAouCmVncmVwICdeKEpvfEFteXxMYXVyaWV8QmV0aClccycgcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNvcnRfY291bnRzLnR4dCA+IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMS5zZWxlY3Rfd29yZHMudHh0Ci4KW01vbiBKdW4gMTAgMjM6MDE6MjEgMjAyNF0KcnVsZSBzZWxlY3Rfd29yZHM6CiAgICBpbnB1dDogcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8yLnNvcnRfY291bnRzLnR4dAogICAgb3V0cHV0OiByZXN1bHRzL2xpdHRsZV93b21lbl9wYXJ0XzIuc2VsZWN0X3dvcmRzLnR4dAogICAgam9iaWQ6IDUKICAgIHJlYXNvbjogQ29kZSBoYXMgY2hhbmdlZCBzaW5jZSBsYXN0IGV4ZWN1dGlvbjsgUGFyYW1zIGhhdmUgY2hhbmdlZCBzaW5jZSBsYXN0IGV4ZWN1dGlvbgogICAgd2lsZGNhcmRzOiBiYXNlX25hbWU9bGl0dGxlX3dvbWVuX3BhcnRfMgogICAgcmVzb3VyY2VzOiB0bXBkaXI9L3RtcAouCmVncmVwICdeKEpvfEFteXxMYXVyaWV8QmV0aClccycgcmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8yLnNvcnRfY291bnRzLnR4dCA+IHJlc3VsdHMvbGl0dGxlX3dvbWVuX3BhcnRfMi5zZWxlY3Rfd29yZHMudHh0Ci4uLgogIGBgYAogIApHbyBhaGVhZCBhbmQgcnVuIHNuYWtlbWFrZSB1bnRpbCAxMDAlIGRvbmUuCgpOb3cgd2UnbGwgcHVsbCB0aGUgZmlsZSBiYXNlIG5hbWVzIGZyb20gdGhlICoqYWxsKiogcnVsZSBpbnRvIHRoZSBjb25maWcgZmlsZToKCmBgYApzZWxlY3Rfd29yZHNfcmVnZXg6IF4oSm98QW15fExhdXJpZXxCZXRoKVxzCgppbnB1dF9iYXNlX2ZpbGVuYW1lczoKICAtIGxpdHRsZV93b21lbl9wYXJ0XzEKICAtIGxpdHRsZV93b21lbl9wYXJ0XzIKYGBgCgpUbyBnZXQgdGhlc2UgdmFsdWVzIGZyb20gY29uZmlnLnlhbWwgaW50byB0aGUKKiphbGwqKiBydWxlLCBJIG5lZWQgdG8gaW50cm9kdWNlIHRoZSBgZXhwYW5kYCBmdW5jdGlvbi4KW2V4cGFuZF0oaHR0cHM6Ly9zbmFrZW1ha2UucmVhZHRoZWRvY3MuaW8vZW4vc3RhYmxlL3NuYWtlZmlsZXMvcnVsZXMuaHRtbCN0aGUtZXhwYW5kLWZ1bmN0aW9uKXt0YXJnZXQ9Il9ibGFuayJ9CmlzIGEgaGVscGVyIGZ1bmN0aW9uIHRoYXQgY29tZXMgd2l0aCBTbmFrZW1ha2UuIEl0J3MgZWFzaWVyIHRvIGV4cGxhaW4gaWYgd2UKc2hvdyBhIGZldyBleGFtcGxlcyBvZiBpbnB1dHMgYW5kIG91dHB1dHM6Cgo+IGBgYApleHBhbmQoIntkYXRhc2V0fS9hLnR4dCIsIGRhdGFzZXQ9WzEsIDJdKQpbJzEvYS50eHQnLCAnMi9hLnR4dCcsICczL2EudHh0J10KLgpleHBhbmQoInthfS57Yn0iLCBhPVsnZmlnMScsICdmaWcyJywgJ2ZpZzMnXSwgYj1bJ3BuZycsICdwZGYnXSkKWydmaWcxLnBuZycsICdmaWcxLnBkZicsICdmaWcyLnBuZycsICdmaWcyLnBkZicsICdmaWczLnBuZycsICdmaWczLnBkZiddCi4KZXhwYW5kKCJyZXN1bHRzL3t4fS5zZWxlY3Rfd29yZHMudHh0IiwgXAouLi4gICAgICAgICB4PVsibGl0dGxlX3dvbWVuX3BhcnRfMSIsICJsaXR0bGVfd29tZW5fcGFydF8yIl0pClsncmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8xLnNlbGVjdF93b3Jkcy50eHQnLCAncmVzdWx0cy9saXR0bGVfd29tZW5fcGFydF8yLnNlbGVjdF93b3Jkcy50eHQnXQogIGBgYAoKYGV4cGFuZGAgYWNjZXB0cyBhIHN0cmluZyBwYXR0ZXJuIHdpdGggbmFtZWQgcGxhY2Vob2xkZXJzIChlLmcuIHtkYXRhc2V0fSkgYW5kCm5hbWVkIGxpc3RzIChlLmcuIGRhdGFzZXQ9WzEsIDJdKTsgaXQgYnVpbGRzIGEgbGlzdCBvZiBzdHJpbmdzIGJ5IGluc2VydGluZyB0aGUKbGlzdCB2YWx1ZXMgaW50byB0aGUgcGF0dGVybi4KClRoYXQgbGFzdCBleGFtcGxlIGFib3ZlIGlzIGV4YWN0bHkgd2hhdCB3ZSBuZWVkIGluIHRoZSAqKmFsbCoqIHJ1bGUuIFRoZSBmaW5hbCAKc3RlcCBpcyB0byBhZGFwdCB0aGUgZXhhbXBsZSBhYm92ZSB0byB1c2UgdGhlIGNvbmZpZyBmaWxlLCBsaWtlIHNvOgoKYGBgCnJ1bGUgYWxsOgogICAgaW5wdXQ6IGV4cGFuZCgicmVzdWx0cy97eH0uc2VsZWN0X3dvcmRzLnR4dCIsIFwKICAgICAgICB4PWNvbmZpZ1snaW5wdXRfYmFzZV9maWxlbmFtZXMnXSkKYGBgCgpUaGUgU25ha2VmaWxlIGlzIG5vdyBmcmVlIG9mIGFueSByZWZlcmVuY2VzIHRvIHNwZWNpZmljIGlucHV0cy4gVGhlIHdvcmtmbG93CmNhbiBiZSBydW4gb24gYSBkaWZmZXJlbnQgc2V0IG9mIGlucHV0cyBieSB1c2luZyBhIG5ldyBjb25maWcueWFtbC4KCmBgYApjb25maWdmaWxlOiAiY29uZmlnL2NvbmZpZy55YW1sIgoKcnVsZSBhbGw6CiAgICBpbnB1dDogZXhwYW5kKCJyZXN1bHRzL3t4fS5zZWxlY3Rfd29yZHMudHh0IiwgXAogICAgeD1jb25maWdbJ2lucHV0X2Jhc2VfZmlsZW5hbWVzJ10pCgoKcnVsZSBzZWxlY3Rfd29yZHM6CiAgICBpbnB1dDogInJlc3VsdHMve2Jhc2VfbmFtZX0uc29ydF9jb3VudHMudHh0IgogICAgb3V0cHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zZWxlY3Rfd29yZHMudHh0IgogICAgcGFyYW1zOiByZWdleD1jb25maWdbInNlbGVjdF93b3Jkc19yZWdleCJdCiAgICBzaGVsbDogImVncmVwICd7cGFyYW1zLnJlZ2V4fScge2lucHV0fSA+IHtvdXRwdXR9IgoKcnVsZSBzb3J0X2NvdW50czoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5jb3VudF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNvcnRfY291bnRzLnR4dCIKICAgIHNoZWxsOiAiIiIKCXNvcnQgLWsxLDFuciB7aW5wdXR9IFwKCSAgICB8IGF3ayAnQkVHSU4ge3twcmludCAid29yZFx0Y291bnQifX0ge3sgcHJpbnQgJDIgIlx0IiAkMX19JyBcCiAgICAgICAgICAgID4ge291dHB1dH0KICAgICAgICAiIiIKcnVsZSBjb3VudF93b3JkczoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zcGxpdF93b3Jkcy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LmNvdW50X3dvcmRzLnR4dCIKICAgIHNoZWxsOiAic29ydCB7aW5wdXR9IHwgdW5pcSAtYyA+IHtvdXRwdXR9IgoKcnVsZSBzcGxpdF93b3JkczoKICAgIGlucHV0OiAiaW5wdXRzL3tiYXNlX25hbWV9LnR4dCIKICAgIG91dHB1dDogInJlc3VsdHMve2Jhc2VfbmFtZX0uc3BsaXRfd29yZHMudHh0IgogICAgc2hlbGw6ICJjYXQge2lucHV0fSB8IHRyIC1jcyAnWzphbHBoYTpdJyAnXG4nID4ge291dHB1dH0iCgpgYGAKCi0tLQoKCiMjIyBTbmFrZW1ha2Ugb24gR3JlYXQgTGFrZXMKCgpBbGwgdGhpcyB0aW1lIHdlJ3ZlIGJlZW4gZXhlY3V0aW5nIHRoZSB3b3JrZmxvdyBvbiB0aGUgbG9naW4tbm9kZXMuIEl0J3MKZ2VuZXJhbGx5IGZpbmUgdG8gZXhlY3V0ZSB0aGUgYHNuYWtlbWFrZWAgcHJvY2VzcyBpdHNlbGYgb24gdGhlIGxvZ2luLW5vZGUsIGJ1dApmb3IgYSBtb3JlIGRlbWFuZGluZyB3b3JrZmxvdyB5b3Ugc2hvdWxkIGV4ZWN1dGUgdGhlIHJ1bGVzIG9uIEdyZWF0IExha2VzIHdvcmtlcgpub2Rlcy4KCkl0J3Mgc3RyYWlnaHRmb3J3YXJkIHRvIGRvIHRoaXMgYnkgdXNpbmcgYSBwcm9maWxlLiBBIHByb2ZpbGUgaXMgZXNzZW50aWFsbHkKanVzdCBhIGxpdHRsZSBjb25maWcgZmlsZSB0aGF0IHRlbGxzIGBzbmFrZW1ha2VgIGhvdyBpdCBzaG91bGQgc3VibWl0IHJ1bGVzIGFzCmpvYnMuIENoZWNrIG91dCBzbmFrZW1ha2UgZG9jcyBmb3IgZGV0YWlscyBvbgpbcHJvZmlsZV0oaHR0cHM6Ly9zbmFrZW1ha2UucmVhZHRoZWRvY3MuaW8vZW4vc3RhYmxlL2V4ZWN1dGluZy9jbGkuaHRtbCNwcm9maWxlcyl7dGFyZ2V0PSJfYmxhbmsifQpvcHRpb25zLiBXZSdsbCBzdGFydCB3aXRoIGEgc2ltcGxlIHByb2ZpbGUgYnVpbHQgZm9yIEdyZWF0IExha2VzOgoKYGBgc2gKY3AgLXIgL25mcy90dXJiby91bW1zLWJpb2luZi13a3NocC93b3Jrc2hvcC9zaGFyZWQtZGF0YS9wcm9maWxlLyBjb25maWcvCmBgYAo8ZGl2IGNsYXNzPSJ3cmFwcGVyIj4KPHRhYmxlIGNsYXNzPSdmaWcnPjx0cj48dGggY2xhc3M9J2ZpZycgc3R5bGU9InRleHQtYWxpZ246IGxlZnQiPmNvbmZpZy9wcm9maWxlL2NvbmZpZy55YW1sPC90aD48L3RyPgo8dHI+PHRkIGNsYXNzPSdmaWcnPjxwcmU+CnByaW50c2hlbGxjbWRzOiBUcnVlCmtlZXAtZ29pbmc6IFRydWUKdXNlLXNpbmd1bGFyaXR5OiBUcnVlCnNpbmd1bGFyaXR5LWFyZ3M6ICItLWNsZWFuZW52IC1CICQocHdkKSIKY2x1c3Rlcjogc2JhdGNoIC0tam9iLW5hbWU9YWxjb3R0X3tydWxlfV97d2lsZGNhcmRzfSAtLWFjY291bnQ9YmlvaW5mX3drc2hwX2NsYXNzIC0tcGFydGl0aW9uPXN0YW5kYXJkIC0tbm9kZXM9MSAtLWNwdXMtcGVyLXRhc2s9e3Jlc291cmNlcy5jcHVzfSAtLW1lbT17cmVzb3VyY2VzLm1lbV9tYn0gLS10aW1lPXtyZXNvdXJjZXMudGltZV9taW59IC0tb3V0cHV0PWNsdXN0ZXJfbG9ncy8leC0lai5sb2cgLS1wYXJzYWJsZQpkZWZhdWx0LXJlc291cmNlczogW2NwdXM9MSwgbWVtX21iPTIwMDAsIHRpbWVfbWluPTEyMF0KcmVydW4tdHJpZ2dlcnM6IFttdGltZV0KbGF0ZW5jeS13YWl0OiAzMDAKam9iczogNQo8L3ByZT48L3RkPjwvdHI+PC90YWJsZT4KPC9kaXY+Cjxici8+CgpJbnZva2luZyBgc25ha2VtYWtlYCB3aXRoIHRoaXMgcHJvZmlsZSBzdWJtaXRzIGVhY2ggcnVsZSBhIGFuIHNiYXRjaCBqb2I6CgpgYGBzaAojIGNsZWFyIG91dCBleGlzdGluZyBmaWxlcyBzbyB0aGVyZSdzIHNvbWV0aGluZyB0byBkbwpybSByZXN1bHRzLyoKCnNuYWtlbWFrZSAtLXByb2ZpbGUgY29uZmlnL3Byb2ZpbGUKYGBgCgpOb3RlIHRoYXQgeW91IGNhbiBhZGp1c3QgdGhlIFNMVVJNIGpvYiBnZW9tZXRyeSBieSBhZGRpbmcgYSAqcmVzb3VyY2VzKiBkaXJlY3RpdmUKdG8gYSBydWxlLiBUaGVzZSB2YWx1ZXMgd2lsbCBiZSBpbmNvcnBvcmF0ZWQgaW50byB0aGUgc2JhdGNoIHJlcXVlc3QgZm9yIHRoaXMKcnVsZS4KCmBgYApydWxlIHNlbGVjdF93b3JkczoKICAgIGlucHV0OiAicmVzdWx0cy97YmFzZV9uYW1lfS5zb3J0X2NvdW50cy50eHQiCiAgICBvdXRwdXQ6ICJyZXN1bHRzL3tiYXNlX25hbWV9LnNlbGVjdF93b3Jkcy50eHQiCiAgICByZXNvdXJjZXM6IGNwdXM9NCwgbWVtX21iPTQwMDAsIHRpbWVfbWluPTUgIAogICAgcGFyYW1zOiByZWdleD1jb25maWdbInNlbGVjdF93b3Jkc19yZWdleCJdCiAgICBzaGVsbDogImVncmVwICd7cGFyYW1zLnJlZ2V4fScge2lucHV0fSA+IHtvdXRwdXR9ICMtLWZha2UtbnVtLXRocmVhZHM9e3Jlc291cmNlcy5jcHVzfSIKYGBgCjxici8+CgojIyMgVXNpbmcgdG11eAoKW3RtdXhdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RtdXgpIGlzIGEgdW5peCB1dGlsaXR5IHRoYXQgbGV0J3MgeW91IApjcmVhdGUgdmlydHVhbCBzY3JlZW5zIGluc2lkZSBvZiBhIHNpbmdsZSBzZXNzaW9uLiAoSXQncyBsaWtlIHRoZSBVbml4IHV0aWxpdHkgCipzY3JlZW4qLCBidXQgbW9yZSBwb3dlcmZ1bCBhbmQgY3VycmVudC4pIEZvciBvdXIgcHVycG9zZXMsIHRoZSBraWxsZXIgZmVhdHVyZSAKb2YgdG11eCBpcyBjYXB0dXJlZCBpbiB0aGlzIHNpbXBsZSB2aWduZXR0ZToKCi0gbG9naW4gdG8gZ3JlYXQgbGFrZXMgYW5kIHN0YXJ0IGEgdG11eCBzZXNzaW9uIG9uIHRoZSBsb2dpbi1ub2RlOiBgdG11eGAKLSBzdGFydCBhIGxvbmcgcnVubmluZyB0YXNrIChlLmcuIGEgc25ha2VtYWtlIHdvcmtmbG93IHdoaWNoIHN1Ym1pdHMgdG8gdGhlIGNsdXN0ZXIpCi0gZGV0YWNoIGZyb20gdGhhdCB0bXV4IHNlc3Npb246IGhpdCBgY3RybC1iYCBgZGAKLSB0dXJuIG9mZiB5b3VyIGNvbXB1dGVyLCBnbyBob21lLCBhbmQgbG9naW4gdG8gZ3JlYXQgbGFrZXMgZnJvbSBkaWZmZXJlbnQgY29tcHV0ZXIKLSByZWF0dGFjaCB0byB0aGF0IHRtdXggc2Vzc2lvbjogYHRtdXggYXR0YWNoYAotIHlvdSB3aWxsIHNlZSB5b3VyIGxvbmctcnVubmluZyB0YXNrIGhhcyBiZWVuIHJ1bm5pbmcgYWxsIGFsb25nLCBjb21wbGV0ZWx5IHVuYWZmZWN0ZWQgYnkgeW91ciB3YW5kZXJpbmdzCgpBbmQgaW4gZmFjdCwgdG11eCBjYW4gYWN0dWFsbHkgZG8gbXVjaCwgbXVjaCBtb3JlLiBIZXJlJ3MgYSBuaWZ0eSBbY2hlYXRzaGVldF0oaHR0cHM6Ly9vcGVuc291cmNlLmNvbS9zaXRlcy9kZWZhdWx0L2ZpbGVzL2dhdGVkLWNvbnRlbnQvb3NkY19jaGVhdHNoZWV0LXRtdXgtMjAyMS42LjI1LnBkZil7dGFyZ2V0PSJfYmxhbmsifS4KCklmIHlvdSBzdGFydCB1c2luZyB0bXV4LCBrZWVwIGluIG1pbmQgdGhhdCB3aGVuIHlvdSBsb2dpbiBpbnRvCmBncmVhdGxha2VzLmFyYy10cy51bWljaC5lZHVgIGl0J3MgYWN0dWFsbHkgZm9yd2FyZGluZyB5b3Ugb24gdG8gb25lIG9mICp0aHJlZSoKbG9naW4gbm9kZXM6IGdsLWxvZ2luMSwgZ2wtbG9naW4yLCBvciBnbC1sb2dpbjMuIChXaGljaCBvbmUgeW91IGdldCBpcyBiYXNpY2FsbHkKcmFuZG9tLikgVGhpcyBtZWFucyBpZiB5b3Ugd2FudCB0byByZWNvbm5lY3QgdG8gYSBkZXRhY2hlZCB0bXV4IHNlc3Npb24sIHlvdSAKaGF2ZSB0byBnZXQgdG8gdGhlIHJpZ2h0IGxvZ2luIG5vZGUuCgpTbywgaWYgeW91IHN0YXJ0IGEgdG11eCBzZXNzaW9uLCBub3RlIHdoaWNoIGxvZ2luIG5vZGUgeW91IGFyZSB1c2luZyB1c2luZyB0aGUgCmBob3N0bmFtZWAgY29tbWFuZDoKCj4gYGBgCiQgaG9zdG5hbWUKZ2wtbG9naW4yLmFyYy10cy51bWljaC5lZHUKICBgYGAKClRoZW4gd2hlbiB5b3UgbmVlZCB0byBsYXRlciByZWNvbm5lY3QgdG8gdGhhdCBzZXNzaW9uLCBpbnN0ZWFkIG9mIGBzc2gKZ3JlYXRsYWtlcy5hcmMtdHMudW1pY2guZWR1YCB5b3UgY29ubmVjdCB0byB0aGUgc3BlY2lmaWMgaG9zdG5hbWUgeW91IGdvdCBhYm92ZToKCj4gYGBgCnNzaCBnbC1sb2dpbjIuYXJjLXRzLnVtaWNoLmVkdQojIGxvZ2luICsgRHVvCiMgbG9naW4gYmFubmVyIChibGFoIGJsYWggYmxhaCkKW2NnYXRlc0BnbC1sb2dpbjIgfl0kIHRtdXggbHMgIyBsaXN0IGFjdGl2ZSB0bXV4IHNlc3Npb25zCjA6IDEgd2luZG93cyAoY3JlYXRlZCBUdWUgSnVuIDExIDIwOjMxOjAzIDIwMjQpIFsxMzN4MjVdCltjZ2F0ZXNAZ2wtbG9naW4yIH5dJCB0bXV4IGF0dGFjaCAjIGF0dGFjaCB0byBteSBzZXNzaW9uCiAgYGBgCgpWb2lsYS4gWW91IGFyZSBiYWNrIGluIHlvdXIgdG11eCBzZXNzaW9uLCByaWdodCB3aGVyZSB5b3UgbGVmdCBpdC4KCkxhc3RseSwgdGhlIHNjcm9sbGJhciBpbiB0aGUgdGVybWluYWwvY29tbWFuZCB3aW5kb3cgZG9lcyBub3Qgd29yayBpbiB0bXV4LiBJZgp5b3UgbmVlZCB0byBzY3JvbGwgaW4gdG11eCwgeW91IGNhbiBoaXQgYGN0cmxgLWBgYmAgYW5kIHRoZW4gYFtgIHRvIGVudGVyIHNjcm9sbAptb2RlLiBZb3UgY2FuIHRoZW4gdXNlIGN1cnNvciBvciBgcGFnZS11cGAsIGBwYWdlLWRvd25gIHRvIG1vdmUgYXJvdW5kIHRoZSB0bXV4CnNjcm9sbC1idWZmZXIuIEhpdCBgcWAgdG8gcXVpdCBzY29sbCBtb2RlIGFuZCByZXR1cm4gdG8gdGhlIGN1cnJlbnQgcHJvbXB0LgoKLS0tCgojIyBQcm8gdGlwcwoKMS4gU3RhcnQgbGVhcm5pbmcgU25ha2VtYWtlIHdpdGggYSBzaW1wbGUgd29ya2Zsb3cuIERvbid0IHN0YXJ0IHdpdGggYSByZWFsbHkKICAgY29tcGxleCB3b3JrZmxvdy4KMi4gSXQncyBvayB0byBidWlsZCB5b3VyIFNuYWtlZmlsZSBmcm9tIHRoZSBmaXJzdCBzdGVwcyB0b3dhcmQgdGhlIGxhc3QsCiAgIGJ1dCByZW1lbWJlciB0aGF0IFNuYWtlbWFrZSAidGhpbmtzIiBmcm9tIHRoZSBsYXN0IG91dHB1dCBiYWNrd2FyZC4KMy4gS2VlcCB5b3VyIGlucHV0cywgb3V0cHV0cywgYW5kIHdvcmtmbG93IGluIHNlcGFyYXRlIGRpcmVjdG9yaWVzLgo0LiBCdWlsZCB5b3VyIFNuYWtlZmlsZSBpbmNyZW1lbnRhbGx5LgogICBhKSBTdGFydCB3aXRoIGEgc2luZ2xlIGlucHV0LgogICBiKSBBZGQgYSBydWxlIGF0IGEgdGltZS4KICAgYykgTWFrZSBzbWFsbCBjaGFuZ2VzIGFuZCAgdXNlIGBkcnktcnVuYCBsaWJlcmFsbHkuCiAgIGQpIE9LIHRvIHN0YXJ0IGJ5IGhhcmRjb2RpbmcgZmlsZW5hbWVzOyBwdXQgdGhlIHdpbGRjYXJkcyBpbiBhZnRlcndhcmQuCjUuIEtlZXAgY29uc2lzdGVudCBuYW1pbmcgY29udmVudGlvbnMuIEluY2x1ZGluZyB5b3VyIHJ1bGUgbmFtZXMgaW4geW91ciBmaWxlCiAgIG5hbWVzIHdpbGwgaGVscCB5b3UgdHJvdWJsZXNob290LiBTb21lIHBlb3BsZSBpbmNsdWRlIG51bWJlcnMgaW4gdGhlIGZpbGUKICAgbmFtZXMgb3IgcGxhY2UgZGlmZmVyZW50IG91dHB1dHMgaW4gZGlmZmVyZW50IGRpcmVjdG9yaWVzLiBDaG9vc2UgYSBwYXR0ZXJuCiAgIHRoYXQgd29ya3MgZm9yIHlvdSBhbmQgYmUgY29uc2lzdGVudC4KNi4gSWYgeW91IHN1YnNldCB5b3VyIGlucHV0IGRhdGEgc28gdGhhdCB5b3VyIHN0ZXBzIGFyZSBmYXN0IGFuZCBsaWdodCwgeW91IAogICBjYW4gcHJvdG90eXBlIHRoZSBTbmFrZWZpbGUgb24gdGhlIGxvZ2luLW5vZGVzLiAoQnV0IGJlIHJlYWR5IHRvIGhpdCBjb250cm9sLUMKICAgaWYgeW91IHdhbmRlciBpbnRvIGEgY29tcHV0YXRpb25hbGx5IGludGVuc2l2ZSBzdGVwLikKNy4gVXNlIHNwYWNlcyBub3QgdGFicy4KOC4gVXNlIGNvbmZpZyBmaWxlcyB0byBhdm9pZCBoYXJkY29kaW5nIHBhdGhzIG9yICJtYWdpYyB2YWx1ZXMiIGluIHRoZSBTbmFrZWZpbGUuCjkuIENoZWNrb3V0IFNuYWtlbWFrZSdzIHN0eWxlZ3VpZGUgb24gW0Rpc3RyaWJ1dGlvbiBhbmQgUmVwcm9kdWNpYmlsaXR5XShodHRwczovL3NuYWtlbWFrZS5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvc25ha2VmaWxlcy9kZXBsb3ltZW50Lmh0bWwjZGlzdHJpYnV0aW9uLWFuZC1yZXByb2R1Y2liaWxpdHkpLiAKICAgSXQgaGFzIGdyZWF0IHJlY29tbWVuZGF0aW9ucyBvbiBob3cgdG8gb3JnYW5pemUgZmlsZXMgYW5kIGRpcmVjdG9yaWVzLgoKLS0tCgojIyBLZXkgaWRlYXMKCiogQSByb2J1c3Qgd29ya2Zsb3cgYXV0b21hdGlvbiBzb2x1dGlvbiBpcyBncmVhdCBiZWNhdXNlOgogICogaXQgc3VwcG9ydHMgc2ltcGxlIChlLmcuIGxpbmVhcikgYW5kIGNvbXBsZXggKGUuZy4gZ3JhcGgpCiAgICB0cmFuc2Zvcm1hdGlvbnMgZXF1YWxseSB3ZWxsCiAgKiBvbmx5IG91dHN0YW5kaW5nIHN0ZXBzIGFyZSBydW4gYW5kIGluZGl2aWR1YWwgc3RlcHMgYXJlIGF0b21pYwogICogaXQncyBkZXNpZ25lZCB3aXRoIHNvZnR3YXJlIGRlcGVuZGVuY3kgbWFuYWdlbWVudCBpbiBtaW5kCiAgKiBpdCdzIGVhc3kgdG8gdHVuZSwgcmVzdWx0aW5nIGluIGhpZ2hlciByZXNvdXJjZSBlZmZpY2llbmN5CiAgKiBpdCdzIHBvcnRhYmxlIGFjcm9zcyBleGVjdXRpb24gZW52aXJvbm1lbnRzCiAgKiBpdCdzIGRlc2lnbmVkIHRvIGdyYWNlZnVsbHkgc2NhbGUgdG8gbWFueSBpbnB1dHMKICAqIGFsbCB0aGUgY2FwYWJpbGl0aWVzIGFib3ZlIGRvbid0IG9ic2N1cmUgdGhlIGFjdHVhbCBkYXRhIHRyYW5zZnJvbWF0aW9uCiogU25ha2VtYWtlIGlzIGEgUHl0aG9uIHByb2dyYW0gdGhhdCBldmFsdWF0ZXMgdGhlIHJ1bGVzIGluIGEgU25ha2VmaWxlIGFnYWluc3QKICB0aGUgZmlsZSBzeXN0ZW0gYW5kIGFwcGxpZXMgcnVsZXMgYXMgbmVjZXNzYXJ5IHRvIHByb2R1Y2UgdGhlIGRlc2lyZWQgb3V0cHV0IAogIGZpbGVzLgoqIFRoZSBTbmFrZWZpbGUgaXMgYSBzaW1wbGUgbWFya2Rvd24gZmlsZSBjb21wb3NlZCBvZiBydWxlcy4gRWFjaCBydWxlIGNvbnRhaW5zCiAgKipkaXJlY3RpdmVzKiogdGhhdCBzcGVjaWZ5IGlucHV0cywgb3V0cHV0cywgY29tbWFuZHMgdG8gcnVuIGZvZSB0aGF0IHJ1bGUuCiogU25ha2VtYWtlIGJ1aWxkcyB0aGUgaW50ZW5kZWQgc2V0IG9mIHJ1bGVzIGludG8gYSAqKmRpcmVjdGVkIGFjeWNsaWMgZ3JhcGgqKiAKICAoREFHKS4KKiBUaGUgKipkcnktcnVuKiogZW5hYmxlcyB5b3UgdG8gc2VlIHRoZSBydWxlcyBpdCB3aWxsIGV4ZWN1dGUgaW4gYWR2YW5jZS4KKiBZb3UgY2FuIHZpc3VhbGl6ZSB0aGUgU25ha2VtYWtlIERBRyBvciBydWxlZ3JhcGggdXNpbmcgYGRvdGAuCiogQ29tbWFuZCBsaW5lIGZsYWdzIHdlIHJldmlld2VkOgogICogLWMKICAqIC1uIC0tZHJ5LXJ1bgogICogLS1kYWcgLS1ydWxlZ3JhcGgKICAqIC0tZm9yY2VhbGwKICAqIC1wIC0tcHJpbnRzaGVsbGNtZHMKCi0tLQoKIyMgRXhlcmNpc2VzCgojIyMgRXhlcmNpc2UgMUE6CgpjZCB0byB0aGUgcHJvamVjdCBwdWJsaWNhdGlvbl9zbmFrZW1ha2VfMQoKR2l2ZW4gdGhpcyBEQUcgY2FuIHlvdSBmaWxsIGluIHRoZSBtaXNzaW5nIHBhcnRzIG9mIHRoZSBTbmFrZWZpbGUgYmVsb3cgYW5kIApyZWdlbmVyYXRlIGEgc2ltaWxhciBEQUcgZmlndXJlPwoKIVtdKGltYWdlcy9Nb2R1bGUwN19leGVyY2lzZTFfZGFnLnBuZykKCkZZSToKCi0gQWxsIHRoZSBydWxlcyB5b3UgbmVlZCBhcmUgaW4gcGxhY2UsIGJ1dCBtb3N0IG9mIHRoZW0gaGF2ZSBpbmNvbXBsZXRlIGlucHV0CmFuZCBvdXRwdXQgZGlyZWN0aXZlcy4KLSBUaGUgc2hlbGwgZGlyZWN0aXZlcyBpbiB0aGlzIFNuYWtlZmlsZSByZWZlcmVuY2UgImV4dGVybmFsIiBzY3JpcHRzCmluIHRoZSB3b3JrZmxvdyBzY3JpcHRzIGRpcmVjdG9yeS4gWW91IHdpbGwgKm5vdCogbmVlZCB0byBjaGFuZ2UgYW55IHNoZWxsIApkaXJlY3RpdmVzIGZvciB0aGlzIGV4ZXJjaXNlLiAKLSBUaGUgYWN0dWFsIHJlc3VsdHMgZmlsZXMgZm9yIHRoaXMgd29ya2Zsb3cgYXJlIG1lcmVseSBzdHVicyBhbmQgbm90IGludGVyZXN0aW5nLiAKR2VuZXJhdGluZyB0aGUgcmlnaHQgU25ha2VmaWxlIC8gREFHIGlzIHRoZSBmb2N1cy4KCmBgYApydWxlIGFsbDoKICAgIGlucHV0OiAncmVzdWx0cy9rbml0X3BhcGVyLmh0bWwnCgpydWxlIGtuaXRfcGFwZXI6CiAgICBpbnB1dDogJ2lucHV0cy9pbnRyby5tZCcsID8/CiAgICBvdXRwdXQ6ID8/CiAgICBzaGVsbDogJ3dvcmtmbG93L3NjcmlwdHMva25pdF9wYXBlci5zaCB7aW5wdXR9IHtvdXRwdXR9JwoKcnVsZSBtYWtlX3Bsb3Q6CiAgICBpbnB1dDogPz8KICAgIG91dHB1dDogPz8KICAgIHNoZWxsOiAnd29ya2Zsb3cvc2NyaXB0cy9tYWtlX3Bsb3Quc2gge2lucHV0fSB7b3V0cHV0fScKCnJ1bGUgbWFrZV90YWJsZToKICAgIGlucHV0OiA/PwogICAgb3V0cHV0OiA/PwogICAgc2hlbGw6ICd3b3JrZmxvdy9zY3JpcHRzL21ha2VfdGFibGUuc2gge2lucHV0fSB7b3V0cHV0fScKCnJ1bGUgZmlsdGVyOgogICAgaW5wdXQ6ID8/CiAgICBvdXRwdXQ6ID8/CiAgICBwYXJhbXM6IGNocm9tb3NvbWU9JzYnCiAgICBzaGVsbDogJ3dvcmtmbG93L3NjcmlwdHMvZmlsdGVyLnNoIHtpbnB1dH0ge291dHB1dH0ge3BhcmFtcy5jaHJvbW9zb21lfScKCnJ1bGUgZG93bmxvYWRfZGF0YToKICAgIGlucHV0OiAnL25mcy90dXJiby91bW1zLWJpb2luZi13a3NocC93b3Jrc2hvcC9zaGFyZWQtZGF0YS9hZ2MtZW5kcG9pbnQvMDA0Mi1XUy9zYW1wbGVfQS5nZW5vbWUuYmFtJwogICAgb3V0cHV0OgogICAgc2hlbGw6ICd3b3JrZmxvdy9zY3JpcHRzL2Rvd25sb2FkX2RhdGEuc2gge2lucHV0fSB7b3V0cHV0fScKCmBgYAoKIyMjIEV4ZXJjaXNlIDFCOgoKVXNlIHBhcmFtcyBhbmQgYSBjb25maWcgZmlsZSB0byBleHRyYWN0IGhhcmRjb2RlZCBhYnNvbHV0ZSBwYXRocyBhbmQgYW55IG90aGVyIAoibWFnaWMgdmFsdWVzIi4KCgojIyMgRXhlcmNpc2UgMUM6CgpFeHRlbmQgdGhlIHdvcmtmbG93IHRvIGNvdmVyIG1vcmUgaW5wdXRzIGFzIGlsbHVzdHJhdGVkIGluIHRoZSBEQUcgYmVsb3c6CgohW10oaW1hZ2VzL01vZHVsZTA3X2V4ZXJjaXNlMUNfZGFnLnBuZykKCgojIyMgRXhlcmNpc2UgMUQ6CgpDb3B5IHRoZSBwcm9maWxlL2NvbmZpZy55YW1sIGZyb20gcHJvamVjdF9hbGNvdHQvYWxjb3R0X3NuYWtlbWFrZSBhbmQgZm9yY2UgCnJlLXJ1biB0aGUgU25ha2VmaWxlIHVzaW5nIHdvcmtlciBub2RlcyBpbnN0ZWFkIG9mIHRoZSBsb2dpbi1ub2RlLgoKLS0tCgojIyBSZWZlcmVuY2VzIGFuZCBsaW5rczoKLSBodHRwczovL3NuYWtlbWFrZS5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvaW5kZXguaHRtbAogIC0gaHR0cHM6Ly9zbmFrZW1ha2UucmVhZHRoZWRvY3MuaW8vZW4vc3RhYmxlL2V4ZWN1dGluZy9jbGkuaHRtbCNhbGwtb3B0aW9ucwogIC0gaHR0cHM6Ly9zbmFrZW1ha2UucmVhZHRoZWRvY3MuaW8vZW4vc3RhYmxlL3NuYWtlZmlsZXMvd3JpdGluZ19zbmFrZWZpbGVzLmh0bWwjZ3JhbW1hcgoKCi0tLQoKfCBbUHJldmlvdXMgbGVzc29uXShNb2R1bGUwNi1pbnRyby10by13b3JrZmxvdy1hdXRvbWF0aW9uLmh0bWwpIHwgW1RvcCBvZiB0aGlzIGxlc3Nvbl0oI3RvcCkgfCBbV3JhcCB1cF0oTW9kdWxlOTlfV3JhcF91cC5odG1sKSB8CnwgOi0tLSB8IDotLS0tOiB8IC0tLTogfAo=